1.0
When writing user interface driven applications, in all but the most simple scenarios you will have parts of the application or of the user interface which will need to be deactivated or made invisible when some predefined condition holds true. There are a lot of instances you can think of when this will be necessary. For example, a web application may have some GUI elements which are only visible to administrators. A logout button will only be visible to logged-in users. Another application may have buttons which are only enabled under certain conditions, such as a Print Document menu item which is disabled if there is no document available to be printed.
Handling this type of conditions is quite common for most applications. They affect whether a component is visible or invisible, or likewise if it gets enabled or disabled. Visibility and enablement of components are both very similar concepts. For the sake of simplicity, the following text will therefore only refer to the visibility of components which does not exclude the applicability to the concept of enablement.
While mostly relatively simple, conditions that affect the visibility of components can become arbitrarily complex. In the simplest case, there is one variable of a system that decides whether a component is visible or not. A typical use case is the role system for an application where the role of a user directly affects an element's visibility in the user interface. Has the user an administrative role, for instance, she will be provided with corresponding components for administration purposes in her user interface.
Such conditions, however, can be a lot more involved. It is not uncommon that a condition controlling a component's visibility is formed of a number of sub-conditions which are interconnected by Boolean operators. Those sub-conditions themselves can be recursively assembled by further sub-conditions. Thus, as a generalization visibility conditions are composed of a tree of sub-conditions.
Let's clarify that with an example. Say you have a desktop application
using user authorization for controlling the access to different
functionalities. One such functionality may be the option to print documents
on a network printer. The menu item for this function is only enabled when
different preconditions hold. Firstly, a document to be printed must be
opened in the application. Secondly, the document to be printed must not be
locked since locked documents are not allowed to be printed. Thirdly, the
current user must be eligible for printing. She is authorized to print
documents if she is member of either the administrator or editor group. The
decision whether or not the printing option is enabled can be expressed with
a Boolean function . Here, the Boolean variables x1, x2,
x3, x4 represent the individual conditions which are relevant
for the print option's enablement. These are as follows: x1 defines whether a printable document is loaded.
x2 is
true
if the document is locked.
x3 is true
if the current user has the
administrator role and x4 is true
if she has the editor role,
respectively. The Boolean function above now becomes
true
if x1 is true
and x2 is false
and one of x3 and x4 or both are true
. In that case the
print option can be enabled.
In a typical application, conditions of this type are controlled by different parts of the application. User roles and authorization are usually handled in a user administration module, whereas the state of a loaded document is monitored by a document manager. A simplistic implementation of the application could now allow each of these modules to directly access the print option's user interface component so that they can enable or disable this component a required. Additionally, each module would need to have a reference to the other modules so that they can query the status of all other involved conditions. Only then an individual part of the application could decide whether it can safely enable or disable the print option's user interface component without violating the rule given by the Boolean function f.
It is easy to see that such a design would lead to a very tight coupling between components yielding a system which will be very hard to maintain. An architecture where different unrelated modules have to know of each other only to be able to manage the visibility of components originating from yet another module should decidedly be avoided.
Managing the set of conditions responsible for the visibility of
components is a cross-cutting concern which should be organized in one
central place. The conditions involved usually refer to a lot of different
parts of an application. So, instead of spreading visibility management
over the whole application, it should be concentrated in a single manager
component. Each part of the application which influences one of the
Boolean variables xi should exclusively be responsible for setting the
value of this variable and nothing else. In the example above, for
instance, the part of the application which loads printable documents
should only need to set the values of the two Boolean variables refering
to whether a document is loaded and whether the current document is
locked. After a document has been loaded by the user, variable
x1 can be set to true
indicating
that a document is available for printing. If that document is locked,
variable x2 is set to true
as well, otherwise
it is set to false
. However, the decision whether the
print option now must be enabled or disabled has to be made
elsewhere.
RoKlib supports you with the kind of problem described above with its package info.rolandkrueger.roklib.util.groupvisibility. This package offers you an API which directly aims at solving the problem of managing the visibility of an application's components centrally. It provides methods for organizing a set of Boolean conditions which refer to different parts of an application. You can assemble these conditions into composites of conditions and manage the visibility of component groups with a central manager class using these composite conditions as a basis.
This manager class is implemented by class
VisibilityGroupManager
in package info.rolandkrueger.roklib.util.groupvisibility. This
is the central class for organizing visibility conditions for an
application. As its name indicates it manages groups of related components
which can be set visible or invisible - and likewise enabled or disabled -
based on a particular predefined condition. Switching the state from
visible to invisible or from enabled to disabled and vice versa is done
automatically by the manager. This does not need to be done manually. You
as an application designer only need to declaratively define the
conditions for such state transitions and to set the corresponding Boolean
variables accordingly when the application state changes.
There are three constituents which take part in centralizing component visibility management. These are in particular the components that can be made visible/invisible or enabled/disabled, a Boolean expression which defines when a component's state has to be switched, and the manager component itself. We're going to look at each of these parts in the following subsections and will discuss how they will be applied, respectively.
VisibilityGroupManager
can manage arbitrary objects
which can be set visible or invisible or enabled and disabled. These may
be graphical user interface components, such as menu items or buttons,
or anything else. The only requirement for these components is that they
offer methods for making them visible and invisible and for enabling and
disabling them. VisibilityGroupManager
therefore accepts as
components all classes which implement the interface
info.rolandkrueger.roklib.util.groupvisibility.IVisibilityEnablingConfigurable
. This interface is
defined as follows.
public interface IVisibilityEnablingConfigurable extends Serializable { void setVisible (boolean visible); void setEnabled (boolean enabled); boolean isVisible (); boolean isEnabled (); }
Classes which implement the methods of IVisibilityEnablingConfigurable can be managed by
the VisibilityGroupManager
. For instance, if you want to
manage Java Swing user interface components with this manager, you can
write corresponding proxy classes for the respective GUI elements. These
proxies then delegate calls to
IVisibilityEnablingConfigurable
's methods to the
corresponding methods of the GUI classes. We'll make that clear with a
concrete example shortly.
If you have more than one component whose states shall be switched under the same conditions, you can combine them as a group of components. This is what gave the manager class for RoKlib's enablement feature its name.
The next important element for managing visibility and enablement
of components is the Boolean expression which defines when the state of
a IVisibilityEnablingConfigurable
component can be toggled.
Such an expression may be very simple, but it can become arbitrarily
complex. Above we have seen an example of such an expression given as
the Boolean function f.
In order to define a Boolean expression for the
VisibilityGroupManager
, the classes of RoKlib's
conditional engine (see Chapter 1, Conditional Engine) are used.
You create these expressions as objects of class
info.rolandkrueger.roklib.util.conditionalengine.BooleanExpression
. There are two Boolean expressions
you can define for each group of components. One expression defines the
condition under which all components of a particular group shall be made
visible. As soon as the expression resolves to
true
, all components of the corresponding group are
made visible. The same applies to the second expression defined for
enablement. If this condition becomes true
, all
components of the group are enabled. Likewise, the reverse applies as
well. If the condition becomes false
, all
components are disabled or are made invisible, respectively.
The VisibilityGroupManager
is the central component
for enablement and visibility management. It accepts
IVisibilityEnablingConfigurable
components and their
corresponding BooleanExpression
objects and takes care of
correctly switching the components' state according to the conditions
given by these expressions.
Components implementing the
IVisibilityEnablingConfigurable
interface can be arranged in
visibility groups. Each group is uniquely identified by a String ID
which can be freely chosen by implementers. These IDs only serve the
purpose of adding individual components into the same group.
Additionally, these IDs are used to refer to a particular visibility
group.
Let us look at a simple example to make clear the usage of a
VisibilityGroupManager
. We will first define a class
JButtonEnablementProxy
which acts as a delegate
between a javax.swing.JButton
object and the
IVisibilityEnablingConfigurable
interface. This class
represents our component to be managed by a
VisibilityGroupManager
object.
public class JButtonEnablementProxy implements IVisibilityEnablingConfigurable { private JButton button; public JButtonEnablementProxy (JButton pButton) { button = pButton; } public void setVisible (boolean visible) { (1) button.setVisible (visible); } // implement all other methods of IVisibilityEnablingConfigurable accordingly }
Method of |
Objects of this class can be added to a
VisibilityGroupManager
as the target objects for visibility
management and enablement. We can now define the concrete instances
which shall be managed by the VisibilityGroupManager
. Let's
define two buttons for printing documents in an application. One button
can be used for immediately printing out a document, the second print
button will first show a printing dialog. By this we get two components
which should be enabled and disabled under the same condition.
Therefore, they can be put into the same group.
// name idendifier for the component group private final static String PRINT_GROUP = "PRINT_GROUP"; (1) // create the button objects private JButton printNow = new JButton ("Print"); (2) private JButton printDialog = new JButton ("Print..."); // create the proxies private JButtonEnablementProxy printNowProxy = new JButtonEnablementProxy (printNow); (3) private JButtonEnablementProxy printDialogProxy = new JButtonEnablementProxy (printDialog); // create a VisibilityGroupManager private VisibilityGroupManager vgManager = new VisibilityGroupManager (); // add the component proxies to the manager vgManager.addGroupMember (PRINT_GROUP, printNowProxy); (4) vgManager.addGroupMember (PRINT_GROUP, printDialogProxy);
Define a constant for the identifier of a group of components which shall all be enabled and disabled under the same condition. You can choose an arbitrary name here. This name is only internally used as a handle on the individual component groups. | |
Create the GUI elements which shall eventually be enabled and
disabled automatically by the
| |
Create the corresponding proxy objects for these GUI
components. They are only needed by the
| |
Finally, add the component proxies to the
|
We have now created a group of components which is managed by the
VisibilityGroupManager
. What is missing now is the Boolean
expression which decides whether this group must be enabled or disabled.
Currently, RoKlib offers only one data structure, which is an implementation of a Ternary Search Tree. This is a map with some interesting properties making it most suitable for very fast prefix matching and spell checking operations.
RoKlib's implementation of a ternary
search tree comes as a class implementing the
java.util.SortedMap
interface with one
particularity. As opposed to common Java maps,
RoKlib's ternary search tree map does only have
one generic class parameter which defines the type of the map's value
object. The data type of the keys is predefined as having to be a subclass
of java.lang.CharSequence
. This is due to
the nature of ternary search trees which don't use integer hash values to
store their map values but instead split up a key's String representation
into single characters to form a sort of a character path to the value.
Hence, the only sensible key type for a ternary search tree is a textual
type.
There are three similar classes available based on the ternary
search tree data structure. Two of them are the aforementioned map in a
case sensitive and case insensitive version
(TernarySearchTreeMap
and
TernarySearchTreeMapCaseInsensitive
). The
third one is simply an implementation of
java.util.SortedSet<CharSequence>
that uses a ternary search tree map as its internal data model. This class
TernarySearchTreeSet
offers the same
features as the ternary search tree map except that it doesn't offer to
associate a value with the String data. Thus, a
TernarySearchTreeSet
is best used when you
only need the prefix matching or spell checking feature of a ternary
search tree.
Let's have a look at the class structure of these map and set classes.
You can see that the functionality specific to ternary search trees
is declared in two interfaces
ITernarySearchTreeQuery
and
ITernarySearchTreeMap<V>
.
One of the most notable feature that a ternary search tree has to offer is that it allows for a very fast prefix matching. That is, given some prefix string, the data structure will provide you all keys stored in the map that start with this prefix.
There are two methods available for a
TernarySearchTreeMap
to do prefix
matching.
Iterable<CharSequence> getPrefixMatch (CharSequence prefix); Iterator<Entry<CharSequence, V>> getPrefixSubtreeIterator (CharSequence prefix);
Even though most modern web application frameworks go to great lengths to hide the context of the web from the programmer and make writing a web application as natural as writing a desktop application, it often cannot be avoided to also address the particularities of the web. One of these areas is dealing with URLs. In spite of modern web technologies such as AJAX, which abstract away the request-response model of the web, you will often want to handle URLs visited by the user directly. RoKlib assists you in doing so in a structured, accurate and well maintainable way. It lets you define action handlers for every URL that can be visited by the user. To these handlers you add action commands which will be executed when a corresponding URL has been visited. These action handlers are arranged in a tree-like structure which mimics the directory strucure of a website's URL structure.
Before we talk about the classes making up the URL action handling framework of RoKlib, we take a look at the general structure of URLs. We're doing this to gain a better understanding of how the library classes work and why they are handling URLs in their particular way.
The general URL structure of a website is inherently hierarchical. A URL can be seen as an equivalent to a file system path where the first part points to a specific directory and the last part selects one particular file in that directory. We can outline the structure of a website similar to a file system. Let's take a contrived website www.example.com as an example.