Using aspects to build GUIs
Abstract
This project's goal was to use AOP to make it easier to develop graphical
user interfaces. Specifically, we start out to build a GUI
from a layout described by an XML file.
We started with
SwiXml, an existing XML-to-GUI translator,
and reworked it and its input files to make the process a bit less
tedious. In particular, we automated a lot of the "support" code:
instance variable declaration and instantiation, adding listeners,
adding import statements, etc.
We developed code to incorporate aspects into the process. Instead of specifying
the full Java class, the user only specifies a small subset of the complete behavior;
most of the framing is generated automatically and inserted into an aspect.
Motivation
With modern GUI frameworks, it is difficult to avoid tangling code for an
application's visual presentation with code for its behaviour. This makes it
difficult to change the visual presentation quickly.
A number of schemes have been developed to address this issue, with one
popular paradigm being to interpret XML as GUI layout code. There are a number
of projects that take XML as input and generate Java/Swing source code as output[@@@],
that generate GTK [source? object? @@@], and that render a GUI application on the spot[@@@].
While the GUI builders cut down enormously on the tedium of developing GUI applications,
and do a good job of separating layout from behaviour, there is still a lot of repetitive
code that must be written. Generating the class framework, adding listeners, declaring variables,
is completely repetitive. With this project, we remove much of that tedium.
Approach
Tool selection
One of our first tasks was to choose a language and toolkit from those available.
We understand how terribly fragile and poorly-documented non-mainstream languages
can be, and wanted to choose a language which had a wide enough distribution that
we would be able to find examples, documentation, and help. With those criteria,
AspectJ was an obvious choice.
We found a number of tools that took XML as input and either generated Java as output or displayed a GUI:
- The documentation for jxmlguibuilder[jxml] was entirely in German. While Sebastian is fluent in German, Ducky can only read it with great difficulty, and Sukesh does not know German at all. The project also looked abandoned.
- The Java Gui Builder[jgb] and JXUL had documentation in English, but the documentation was very poor.
- Luxor [luxor] is pretty much the Cadillac of XML->GUI translators. It uses XUL as its XML dialect, and has a very rich and active development community. However, Luxor looked excessively complicated. In addition to the XUL translation, it has a web server, a portal engine, a template engine, a Python interpreter and more. We did not want to have to wade through all the extras in order to get at the XML translation kernel.
- Glade [glade] is a robust project with a GUI interface to generate the GUI code, but unforunately uses GTK widgets and not Swing ones.
- Koala seems to be built on Java Beans, which seemed wrong for our project.
- SWI XML(AKA SwiXml)[swixml] is, beside Luxor, one of the most used XML to GUI Tools, and is built on Java Swing. It has its own XML dialect whose numerous pre-defined tags and attributes can be used intuitively for experienced Swing Developers. The XML files are translated at run-time. Furthermore it provides also a useful documentation how to use tags and attributes for inexperienced Swing Developers.
- Thinlet seemed very similar to Swix, but not as good.
- gui4j was limited in number of attributes for objects is limited.
After careful thought we decided to use Swix as our XML translator
and extended it so that it works with
AspectJ.
Out of the box,
SwiXml
takes two input files: an XML file which describes the GUI layout, and a Java class that
does some initialization, defines actions used by the GUI, has a reference to the XML-file
and finally builds the GUI at runtime.
With
SwiXml,
- Each XML tag name corresponds to a JComponent class, e.g. the tag "button" corresponds to the JButton class.
- Each instance of a XML tag corresponds to an instance of a that JComponent class.
- Each tag has many predefined attributes (e.g. size, font, border), which usually cause a call
to a similarly-named set*() method in the corresponding JComponent class during inialization.
The most important tag for the rest of this document is the "id" attribute. The id attribute in the XML
file is used to connect the similarly-named instance variable in the Java class.
The
name of the instance variable in the Java file pointing to the action
must be the same as
the id attribute for that component in the XML file.
For example, if a button bt is declared in the XML file like so:
<button id="bt1" text="some text"/>
then when the button is pushed, then
bt1.setText("some text");
in the Java file will execute.
Implementation
With our tools, the process is very similar to the stock
SwiXml process, with the following differences:
- briefer Java code composition (the "methods" file)
- mandatory ids
- additional translation step to generate aspect and Java files for SwiXml
- methods instead of actions
- slight modifications of SwiXml code
Methods file
Instead of writing a complete Java file, the developer writes
only the methods that perform actions.
Instead of the complete class shown in Figure 1, the developer would only write the code shown in
Figure 2.
import org.swixml.SwingEngine;
import javax.swing.*;
import java.awt.event.ActionEvent;
public class HelloWorld {
public JButton button1;
private HelloWorld() throws Exception {
new SwingEngine( this ).render( "xml/helloworld.xml" ).setVisible( true );
}
public AbstractAction do_something = new AbstractAction() {
public void actionPerformed( ActionEvent e ) {
System.out.println("button1 pushed");
}
};
public static void main( String[] args ) throws Exception {
new HelloWorld();
}
}
Figure 1: Java file
private void do_something(ActionEvent e)
{
System.out.println("button1 pushed");
}
Figure 2: Methods file
The methods file contains only the actions of the GUI elements, not the class definition, instance variable declarations,
instantiations, or addListener code.
Methods instead of actions
The XML file, when using stock
SwiXml, uses Action attributes to specify how to wire up
the components, expecting the actions to be in the .java file, something like this:
<button id="bt1" text="button1" Action="do_something">
We put the behavioural code in the .aj file, so we use the "method" attribute
instead, something like this:
<button id="bt1" text="button1" method="do_something">
This required some modification of
SwiXml.
Id field
We added an id field to all of the JComponents, and that meant that we needed an id
in the XML for all components.
This meant that we needed to subclass all of the interesting JComponents. We used the
convention of replacing the leading "J" with a leading "X", so JButton now has a subclass
"XButton".
SwiXMl has a class called
SwingTagLibrary which maps XML tags and their JComponent classes
(e.g button->JButton). Because we changed JComponents to XComponents, we needed
to also change all the associated mappings.
The constuctor for each wrapper calls the super constructor, then assigns the
id field to the component's id. The component's id comes from
a Parser class variable which happens to have the correct id in it at this point in time.
One implication is that the id attribute in the XML file is now required for all components instead of optional.
dummyParserMethod()
The Parser class is the heart of the
SwiXml framework since it controls the whole mechanism. It parses through
the XML file and creates the corresponding Swing objects to each tag with all settings the developper specified within
the attributes of each tag.
At two points in the Parser class, the components are created. One point is the general case and the other point is a
special case for
ButtonGroups. Right after this component instantiation, we inserted a method called
dummyParserMethod() which takes the component and its id as arguments.
The dummyParserMethod()'s
only function is to pass the component and its id into the aspect, so that
the aspect can wire everything up properly; it was the best way we saw to provide instance-level behavior.
The dummyParserMethod() feels somewhat kludgy, but works extremely well. Our alternatives were
- to use an aspect language (like EOS[eos]) with built-in instance-level aspects
This has been necessary the different GUI components have
different actions associated with them on an instance-by-instance basis. In order for our advice to be able to distinguish
between different instances, we needed a way for an instance-level identifier to get passed around. Instead of trying to modify
all the JComponent constructors and all the places in the Swix code where JComponent constructors are called, we added
this call to a dummy method after the component was created.
Separate translation step
We wrote an XSLT translator that takes the XML and methods files, and generates an aspect file and a Java file
for input to
SwiXml in a second pass. (The XML file is unchanged from the first pass to the second pass.)
In the aspect file, for each XComponent, the XSLT translator will
+ declare a local variable with the same name as the id attribute for that object, as specified in the XML file (see top of Figure 3)
+ instantiate the variable (see Figure 3)
+ add a listener whose action has the same name as the method attribute for that object, as specified in the XML file
(see Figure 4).
Those aspects could have been combined, but while the instance variable creation is the same for all types
of components, the listener action is slightly different for different components. For example, JLabels do not
have listeners, JButtons use addActionListeners(),
JCheckBoxes use addItemListener(), and JTrees use addTreeSelectionlistener().
[...]
XButton bt1; // declare
pointcut newInstance(Object o, String aName): (call(* Parser.dummyParserMethod(Object, String)) && args(o, aName));
[...]
after(Object o, String aName): newInstance(o, aName)
{
if(aName.equals("bt1"))
{
bt1 = (XButton)(o); // instantiate
}
}
Figure 3
after(Object o, String aName): newInstance(o, aName)
{
if(aName.equals("bt1"))
{
// add listener
bt1.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
do_something(e);
}
});
}
}
[...]
private void do_something(ActionEvent e)
{
System.out.println("button1 pushed");
}
}
Meanwhile the Java file, which is extensive in stock
SwiXml,
is very small after our changes. The only difference
in the Java file between two different runs with two different sets of inputs will be one string
which contains the name of the XML file.
Note that the XSLT file is set up so that if the user forgets to specify a method, it writes
a placeholder method to the aspect that merely prints out a warning message, advising the
user that there is a method missing.
Lessons learned
We faced a number of challenges in this project.
Documentation / configuration
One of our reasons for choosing
AspectJ was because it had
a vibrant ecosystem and strong local support system. We
are very glad that we chose
AspectJ, as we needed the support
system. The documentation for
AspectJ and AJDT,
while not completely threadbare, did have some holes in them.
We had significant configuration issues.
Instance-level behaviour
We had a number of challenges getting pointers to
components as they were made (so that we could add a listener
to them).
We tried to put advice before or after execution of the JComponent constructors,
but because we didn't have the source, we were not able to do before() or
after() advice to execution() joinpoints. We could fire before() or after()
advice on call()s to constructors, but we couldn't figure out how to get a
pointer out of the advice. Even if the constructor always was put into
a variable with a known name, e.g. moo
JButton moo = new JButton("Mooooooooo!");
a before() happened before the JButton existed, and the after() happened
before the assignment of the new JButton to the variable moo.
We considered subclassing all the constructors so that we would
pass the id in to each constructor, but that was not tractable. There were
too many classes with too many constructors in too many too obscure and
twisted places in the
SwiXml code to do that.
We attempted load-time weaving, but had trouble configuring load-time
weaving. (See "Documentation/configuration" above.)
(This hurdle came up while our entire
AspectJ support structure
was in Europe, and see "Documentation/configuration" above.)
We eventually came up with the dummyParserMethod() technique described above,
and that worked quite well. It seemed to go against the grain of AOP to insert
a marker like that, but this whole project seemed to be going against the
grain of AOP (see more below).
After our
AspectJ support team returned from conferences, we discovered
that we could have used the return value from around() advice, but we'd already
started down one road and didn't see a particular reason to stop.
One thing that we spent quite a bit of time on was setting up
the code -- with wrappers and so on -- so that we would have the id
available to us later. We weren't quite sure what we would need the
id for, but we figured that one of the complex components like JTree
or JTable would need it. We figure that those would come up late in
the semester, when we wouldn't have time, so we should work on it now.
We were wrong. That code was entirely unneeded. We left it in only
to show that we actually did something.
XSLT
XSLT, while useful, also has its limitations.
Very late in the term, we realized/discovered that XSLT is case
sensitive, while
SwiXml is not. This was not what we intended.
XSLT also is limited in what it can do. It doesn't have a particularly
obvious way to "store up" information for later disgorging, for example.
A different translation tool might have been better.
Methods vs. actions
Standard
SwiXml uses Actions, while we use methods. Actions are more
powerful, but
SwiXml expects them to be in the .java class. We could
have rewritten the
SwiXml code to look in the aspect file, but that
doesn't seem very AOP. We could have easily put everything in the .java
file, but that wouldn't have been very interesting pedagogically for
an AOP class. Perhaps we could have modified
SwiXml to not try to
wire up Actions, and write the translator to wire up Actions in
the aspect.
One of our biggest issues was that
SwiXml worked too well at
separating layout and behaviour. There
really wasn't a lot of value that aspects added. We could have
just as easily written our translator to create the .java file
as the .aj file.
Validation
@@@
Talk about difficulty of testing GUIs
http://www.evolutif.co.uk/GUI/TestGui.html
Related work
@@@
Future work
As mentioned above,
SwiXml out of the box did a pretty good job of
separating layout and behaviour. We don't think that there is
a compelling pragmatic (as opposed to pedagogical) reason to do
extensive work on
SwiXml.
From a pedagogical standpoint, it might be interesting to rip out
all of the wrappers and revert to using JComponents (without an
instance-level id). We think we could rip out dummyParserMethod() as
well, and use around() advice; it might be interesting to try.
Also from a pedagogical standpoint, it might be interesting to
attempt to convert this from a two-pass process (translation +
SwiXml)
into a one-pass process. We had thought this would be easy, but
we now believe that we would have to get extremely intimate with
the JIT compiler, the weaver, and the Java reflection classes.
References
[jxml]
http://www.guibuilder.de/
[jgb]
http://jgb.sourceforge.net/index.php
[luxor]
http://luxor-xul.sourceforge.net/
[glade]
http://glade.gnome.org/
[swixml]
http://swixml.org/
[eos] EOS
Need more references
See "Association Aspects" paper?