Tags:
create new tag
view all tags

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.

Native SwiXml

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.

SwiXml quality

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?
Topic revision: r1 - 2006-04-22 - TWikiGuest
 
This site is powered by the TWiki collaboration platform Powered by PerlCopyright © 2008-2024 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback