Buoy

A Better User Interface Toolkit

by Peter Eastman
Version 1.9, released May 1, 2009

Introduction

Buoy is a toolkit for creating user interfaces in Java programs. You can think of it as a replacement for Swing and AWT, although that is not entirely correct. Buoy is built on top of Swing, so when you use Buoy to create a user interface, Swing components are still being created behind the scenes. A better description might be to say that Buoy is a replacement for the Swing API, although it is really more than just that.

Perhaps the best way to introduce Buoy is to show a simple program which uses it. Here is a program that displays a window with the message, "Hello World!", and quits when the user clicks the OK button. The version on the left uses Buoy, and the one on the right uses Swing.

import buoy.event.*;
import buoy.widget.*;

public class HelloWorld
{
  public static void main(String args[])
  {
    BFrame f = new BFrame();
    BorderContainer content = new BorderContainer();
    f.setContent(content);
    content.add(new BLabel("Hello World!"), BorderContainer.CENTER);
    RowContainer rc = new RowContainer();
    content.add(rc, BorderContainer.SOUTH);
    BButton b = new BButton("OK");
    rc.add(b);
    b.addEventLink(CommandEvent.class, new Object() {
      void processEvent()
      {
        System.exit(0);
      }
    });
    f.pack();
    f.setVisible(true);
  }
}
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class HelloWorld
{
  public static void main(String args[])
  {
    JFrame f = new JFrame();
    Container content = f.getContentPane();
    content.add(new JLabel("Hello World!"), BorderLayout.CENTER);
    JPanel p = new JPanel();
    content.add(p, BorderLayout.SOUTH);
    JButton b = new JButton("OK");
    p.add(b);
    b.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent ev)
      {
        System.exit(0);
      }
    });
    f.pack();
    f.setVisible(true);
  }
}

For such a simple program, there really is very little difference between the two versions. There is almost a perfect line-to-line correspondence between them. At a first glance, you might think I have done little beyond changing some "J"s to "B"s.

On closer inspection, however, you will note some subtle differences. For example, the Buoy version sets the window's content pane rather than getting it. And then there is the very curious form of that event handler. We will look at these differences in detail later on.

Why would you want to use Buoy instead of the standard Swing API? Quite simply, because it's better. It is easier to learn and easier to use. It lets you do what you want to do with fewer lines of code. It produces programs which are simpler, more readable, and easier to maintain.

This document describes the design of Buoy, how it is different from Swing and why I chose to implement it the way I did. By the time you finish reading it, I hope I will have convinced you that Buoy is Simply Better.

The current version is 1.9. It is stable and suitable for production use. Please report bugs at the Buoy project page on SourceForge.

Components and Widgets

A Widget is in Buoy what a Component is in AWT/Swing. It represents a graphical object on the screen. In fact, there is an exact mapping between Widgets and Components. For every Widget, a corresponding Component is created to "implement" that Widget. In a sense, you could think of a Widget as simply being a wrapper around a Component, but I don't recommend it. It is better to think of the Widget as being the graphical object itself, and of the Component as simply a side effect of how the Widget is implemented behind the scenes.

In all cases, Widgets present a far simpler API than the corresponding Components. For example, the J2SE 1.4.2 version of JComponent has 306 public methods. The current version of Widget has 37.

Where did all those method go? Well, about 40 of them are deprecated and should not really be counted, but that is only a small fraction of the total methods. A number of methods are related to the Swing event handling mechanism: all of the addXListener(), removeXListener(), and processXEvent() methods. Buoy uses a different event handling mechanism which does not require separate methods for every event type.

Some methods are simply redundant. JComponent has ten different methods for querying the current location and size of a component (not including deprecated methods like bounds() and size()). Widget has one.

Finally, there are some methods that are simply artifacts of bad design, and have no business being there in the first place. The get/set alignment methods are excellent examples of this.

That still leaves a lot of methods which provide legitimate but infrequently used features. Swing is a very complicated library, but also a very powerful one. Some of the complexity is simply the result of bad design, but some of it is an inevitable result of the huge number of features that it offers.

Buoy does not attempt to encapsulate every last feature of Swing. Instead, it offers a simple API for the subset of features that are used 90% of the time. For the less frequently used features, you must still access Swing directly. Fortunately, that is a very easy thing to do. In particular, you can call getComponent() on any Widget to get the Component used to implement it.

As an example of this, consider the JList and BList classes. There are two ways to set the contents of a JList: you can set the entire content at once, by passing in an array or Vector; or you can create an object that implements the ListModel interface, set it as the model for the list, and manipulate the list contents indirectly through the model. The former method is too limited for anything except the very simplest cases. This means that in practice, nearly any use of a JList requires working with a ListModel.

In contrast, BList provides a default model which is powerful enough for the vast majority of cases, then hides the fact that there is a model involved at all. You simply call add() on the BList to add an item to the list, remove() to remove an item, and so on. BList also allows you to set a custom model for the rare cases where you actually need one, but usually that is not necessary.

What about even less frequently used features: custom ListSelectionModels, ListCellRenderers, and the like? BList does not provide methods for setting these directly, but that is not a problem. Simply call getComponent() on the BList to obtain the underlying JList, then call the appropriate methods exactly as you normally would.

A more extreme example is provided by JTable and BTable. Even a very simple use of a JTable requires dealing with a complex API and a large number of objects, including the JTable itself, a TableModel, a TableColumnModel, two different ListSelectionModels, and a whole array of TableColumn objects. Buoy hides that complexity, and replaces it with a single BTable object with a simple, easy to use API. The complexity is still there when you really need it. Usually, you won't.

Another important link between AWT/Swing and Buoy is provided by the AWTWidget class. This is a Widget that acts as a wrapper around an arbitrary Component. If you have a legacy or third party Component, you are free to use it in Buoy programs. Simply wrap it in an AWTWidget, and then treat it like any other Widget.

Laying Out Widgets

Swing separates the concept of a Container (any Component which holds other Components) from a LayoutManager (which determines how those Components should be arranged inside their parent container). In theory, any LayoutManager can be used with any Container, offering enormous flexibility in how Components are layed out.

The reality is very different from the theory. In practice, the vast majority of Swing containers are JPanels. The few exceptions to this rule (such as JRootPane and JScrollPane) typically have their own "personal" LayoutManagers to which they are intimately bound. In short, most LayoutManagers have one, and only one, Container class with which they are ever used.

Furthermore, this unused flexibility comes at a price. Components must be added to Containers with the same methods, regardless of what LayoutManager will be used to arrange them. This requires that those methods be extremely generic in nature: add(Component comp, Object constraints). All layout information must be shifted to a separate "constraints" object. Of course, there is nothing to prevent you from passing a constraints object which is entirely inappropriate for the LayoutManager in use. In fact, except by reading the documentation, there is no way to determine what type of object the current LayoutManager expects. Nor is there any way to identify a particular object as being intended for use in layout. There is no "LayoutConstraint" interface which constraint objects must implement, or any other way for them to identify themselves.

In contrast, Buoy combines the Container and LayoutManager into a single WidgetContainer class. This is an abstract class, with subclasses that layout their children in different ways: ExplicitContainer, which lets you explicitly set the position of each Widget; BorderContainer, which is similar to a BorderLayout; GridContainer, which is similar to a GridLayout; and so on. As a result, each container can provide add() methods that are appropriate for that container. The result is a cleaner API that requires less information to be placed in separate objects.

In addition, Buoy's WidgetContainers are much more consistent than Swing's LayoutManagers. Swing often feels as if its pieces were written by different people who never spoke to each other. Every LayoutManager is completely different from every other. Learning to use a BorderLayout teaches you nothing about how to use a GridBagLayout, which in turn teaches you nothing about how to use a BoxLayout.

Once you learn to use one WidgetContainer, on the other hand, you are much of the way to understanding all of them. For example, there is a single LayoutInfo class that many different WidgetContainers use to store detailed layout information for a particular Widget. They allow you to set a default LayoutInfo for everything in the container, or to override it one Widget at a time. The result is a much simpler, cleaner, and more consistent API.

Events, part I: What Is An Event?

Event handling is the area where Buoy differs most dramatically from AWT/Swing. I will therefore break it up into a few pieces, and examine each piece separately.

Swing uses one class for each category of events. For example, MouseEvent is used for all events relating to the mouse, such as being pressed, released, moved, dragged, etc.

Buoy, on the other hand, uses a separate class to represent every distinct type of event: MousePressedEvent, MouseReleasedEvent, and so on. In most cases, the Buoy event classes extend the corresponding Swing classes. For example, all mouse related events subclass java.awt.event.MouseEvent. This simplifies the interaction between Buoy and Swing. In addition, Buoy defines its own parent classes for various categories of events. For example, MousePressedEvent subclasses WidgetMouseEvent, which in turn subclasses MouseEvent. In most cases, you should use Buoy's parent classes (all of which implement the WidgetEvent interface) whenever you want to refer to a category of events.

You might expect that this would lead to a huge number of classes for representing different types of events, but in practice it does not. This is due to another design principle of the Buoy event model, which eliminates entire categories of events:

Buoy events always represent user actions, never actions taken by the program itself.

That is an important distinction, and one which AWT/Swing completely fails to make. When an event is received, it is often impossible to determine whether that event was generated in response to a user action (i.e. the user resized a window by hand) or a program action (i.e. the program called setSize() on it). This is a common source of bugs in Swing programs, and a source of much frustration and grief among Java programmers. Furthermore, the handling of this issue is not even consistent: some methods of some objects generate events when they are invoked, but others do not. A general rule seems to be that lightweight Components generate events in response to method calls, but heavyweight ones do not. Even here, however, there are exceptions that do not follow any clear pattern. For example, calling setText() on a TextField does generate a TextEvent.

In Buoy, there is no uncertainty. Events always represent user actions. For communication between different parts of the program, UI based event handling is rarely the best approach, and usually represents a misuse of the Model-View-Controller design. If you absolutely require it, you can always call getComponent() on a Widget and add a listener directly to the Component, but the situations where that is actually needed should be few and far between.

There are a few exceptions to this rule: FocusGainedEvent and FocusLostEvent are always sent when a Widget's focus state changes, whether this resulted from a user action (like clicking on a Widget), or a program action. Likewise, WindowActivatedEvent and WindowDeactivatedEvent are always sent when a window becomes active or inactive, regardless of the reason. These exceptions are necessary due to the nature of focus management: the focus state of one Widget can be changed by calling requestFocus() on a different and completely unrelated Widget. This means that monitoring events is really the only practical way for a Widget to detect when its focus state has changed and update its appearance accordingly.

Another way of looking at this is to say that the focus state of a Widget, and the activation state of a window, are not really properties of that Widget at all; they are properties of the windowing environment as a whole (which Widget events will be sent to). Perhaps a more accurate statement of the design principle is this: "Buoy events always represent user actions or programmatic changes to the global state of the windowing environment, but never programmatic changes to the internal state of a particular Widget."

Events, part II: What Happens To An Event?

Event notification in Swing is done through statically implemented event listener interfaces. If a class wants to be notified when a button is pressed, it implements the ActionListener interface, which requires it to provide a method with a particular signature that will be called whenever an ActionEvent occurs:

public void actionPerformed(ActionEvent ev)
{
  ...
}

It then requests notification of events by calling an appropriate method on the button:

button.addActionListener(this);

Buoy handles event notification through dynamically linked listener methods. For example, if a class wants to be notified of button presses, it could implement a method like this one:

public void buttonPressed(CommandEvent ev)
{
  ...
}

It would then request notification of events by adding an "event link" to the button:

button.addEventLink(CommandEvent.class, this, "buttonPressed");

The three arguments to addEventLink() are the type of events the listener wants to be notified about, the object to notify, and the name of the method to call on it. The method's argument type need not exactly match the class passed to addEventLink(). It merely needs to be compatible with it. For example, the method could have been declared as

public void buttonPressed(WidgetEvent ev)

or even as

public void buttonPressed(Object ev)

Similarly, you can request notification of entire categories of events at once by passing a superclass or interface to addEventLink(). For example, you could direct all mouse related events to a method by calling

widget.addEventLink(WidgetMouseEvent.class, this, "handleMouseEvent");

or absolutely all events of any sort by calling

widget.addEventLink(Object.class, this, "eventOccurred");

If you omit the third argument, Buoy will look for a method with the default name "processEvent". This is a convenient shortcut when using anonymous inner classes as event listeners:

button.addEventLink(CommandEvent.class, new Object() {
  void processEvent(CommandEvent e)
  {
    ...
  }
});

The argument passed to the event listener can be omitted if the method does not care about seeing the event object. For example, if a method is only used for handling presses of one specific button, it can simply be declared as

private void buttonPressed()

This can allow incredibly concise event handling in certain cases. If the desired response to an event is to invoke a single method which already exists, there often is no need to write an event handler at all. For example, suppose you want a window to be disposed when the user clicks its close box. This can be accomplished with a single line of code:

window.addEventLink(WindowClosingEvent.class, window, "dispose");

Events, part III: But Why?

Buoy's event handling system is certainly different from Swing's, but is it better? If you have never used any UI library other than AWT/Swing, Buoy's system may seem strange, awkward, and possibly even "un-Java-like". Why would you want to use it?

The answer, as with all of Buoy, is simply, "Because it's better!" It is more powerful and flexible. It produces simpler programs with less wasted code. Your programs will have fewer bugs, and be easier to maintain.

That is quite a claim to make, and I had better be able to back it up. Believe me, I am. Let me present a number of different examples, each one showing a different way in which Buoy's event mechanism is better.

One Method, Many Events

Consider a common situation: you want to display a popup menu whenever the user performs the platform-specific trigger action (right-clicking on Windows, control-clicking on MacOS, etc.) on a particular Widget. In theory, any mouse event can be a popup trigger, so you really should check all of them. In practice, only mouse pressed and mouse released events are commonly used as popup triggers, so it is probably sufficient to check only those.

How would you do this with Swing? The most straightforward way is to implement MouseListener, and handle each of these event types:

public void mousePressed(MouseEvent ev)
{
  if (ev.isPopupTrigger())
  {
    // Do whatever is necessary to show the popup menu.
  }
}

public void mouseReleased(MouseEvent ev)
{
  if (ev.isPopupTrigger())
  {
    // Do whatever is necessary to show the popup menu.
  }
}

In the above code, "Do whatever is necessary to show the popup menu," could mean something fairly complex (i.e. building or configuring various menu items based on the click location and the current state of the program). Having this code exactly duplicated in two different methods is very bad style, and a great opportunity for bugs. For example, someone might make a change to one of these methods, but forget to make the corresponding change to the other one. A better solution is to move this logic into its own method:

public void mousePressed(MouseEvent ev)
{
  if (ev.isPopupTrigger())
    showPopupMenu(ev);
}

public void mouseReleased(MouseEvent ev)
{
  if (ev.isPopupTrigger())
    showPopupMenu(ev);
}

private void showPopupMenu(MouseEvent ev)
{
  // Do whatever is necessary to show the popup menu.
}

This is an improvement, but it is still far from ideal. We are using three methods to implement a single operation, which adds clutter to the code and increases the opportunities for bugs.

In Buoy, we can do it all with a single method:

private void showPopupMenu(WidgetMouseEvent ev)
{
  if (ev.isPopupTrigger())
  {
    // Do whatever is necessary to show the popup menu.
  }
}

And that is it. We can direct all mouse events to that method with a single line:

addEventLink(WidgetMouseEvent.class, this, "showPopupMenu");

or, if you want to restrict it to only sending mouse pressed and mouse released events,

addEventLink(MousePressedEvent.class, this, "showPopupMenu");
addEventLink(MouseReleasedEvent.class, this, "showPopupMenu");

In short, Buoy allows you to handle multiple event types with a single method.

Many Methods, One Event Type

Now let's consider the converse situation: using different methods to handle the same type of event. For example, suppose you are implementing a window with a menu bar. A common design is to use a single class (most often the window itself) as the event listener for all of the menu items. In Swing, the most straightforward way of doing this is to use the "action command" to distinguish between different menu items. You would create the menu items as follows:

JMenuItem mi;
editMenu.add(mi = new JMenuItem("Copy"));
mi.setActionCommand("copy");
mi.addActionListener(this);
editMenu.add(mi = new JMenuItem("Cut"));
mi.setActionCommand("cut");
mi.addActionListener(this);
editMenu.add(mi = new JMenuItem("Paste"));
mi.setActionCommand("paste");
mi.addActionListener(this);
...

Then you would dispatch the different events to appropriate methods based on the action command:

public void actionPerformed(ActionEvent ev)
{
  String command = ev.getActionCommand();
  if ("copy".equals(command))
    doCopy();
  else if ("cut".equals(command))
    doCut();
  else if ("paste".equals(command))
    doPaste();
  ...
}

How would you do this in Buoy? You can specify the method to handle each menu item at the same time you create it:

BMenuItem mi;
editMenu.add(mi = new BMenuItem("Copy");
mi.addEventLink(CommandEvent.class, this, "doCopy");
editMenu.add(mi = new BMenuItem("Cut");
mi.addEventLink(CommandEvent.class, this, "doCut");
editMenu.add(mi = new BMenuItem("Paste");
mi.addEventLink(CommandEvent.class, this, "doPaste");

And that is it! There is no need for a separate method to dispatch the events from different menu items to the correct handler methods. Buoy takes care of that for you. There are fewer separate pieces of code involved in handling each command, which makes the program more readable, easier to maintain, and less susceptible to bugs.

Only the Methods I Want!

Suppose an object wants to be notified whenever the user clicks the mouse in a certain Component. In Swing, it does this by implementing the MouseListener interface, and placing the appropriate logic in the mouseClicked() method. Unfortunately, you cannot provide only that method. MouseListener defines five different methods, and so you must implement all five of them, even though you only really want one. This adds clutter to your program and wastes your time by making you write unwanted code that will never be used.

You can work around this problem by subclassing MouseAdapter, which provides empty default implementations of all the listener methods. This is not always an acceptable solution, however. The class in question may already extend something else, which rules out the possibility of subclassing MouseAdapter. Alternatively, you could put the event handling code into an inner class which extends MouseAdapter, but this too is not always possible: if the event handling is done by an inner class, then it is impossible for a subclass to override it and handle the event in a different way. Getting more complicated, you could have an inner class which extends MouseAdapter, implements mouseClicked(), and then passes the event to a public or protected method of the main class for handling. Here again, we find ourselves adding extra layers to the code which complicates the logic and introduces new opportunities for bugs.

In Buoy, none of this is an issue. You implement handlers for only the event types you actually care about. You never need to write empty methods simply because "the interface requires it".

Invasion of Privacy

In order to implement one of the Swing event listener interfaces, all of the event handling methods must be declared public. That means that they become part of the public API of the class. In most cases, these methods are really intended "for internal use only", and it would be inappropriate for any other class to call them. In any reasonable design, these methods should be declared private or protected, and making them public pollutes your API.

You can work around this problem by using a private inner class as the event listener, then having it pass the events on to private or protected methods on the main class. Once again, we find ourselves forced to add extra layers to the code (with the attendant clutter, reduction in maintainability, and opportunities for bugs) simply to work around the deficiencies of the Swing event handling mechanism.

In Buoy, you are free to make your event handling methods private or protected. In fact, I have done so in some of the examples given earlier, just to see if you would notice! No extra layers are required to keep your API clean and expose only the functions you really want to expose.

Please Sir, Can I Have Some More Events?

Both the Swing and Buoy event handling mechanisms are extensible. You can define new types of events, and new Components/Widgets that generate those events. As an example, suppose you are implementing a new Component that can recognize mouse "gestures", patterns of motion that are more complex than simple clicks or drags. You want to send out an appropriate event whenever the user performs a gesture. Here is how you would do it with the Swing event model:

  1. Create a GestureEvent class to represent a gesture.
  2. Create a GestureListener interface, which must be implemented by any object that wants to receive GestureEvents.
  3. Any Component that generates GestureEvents must internally maintain a list of GestureListeners, probably as a Vector or ArrayList. It must implement an addGestureListener() method for adding listeners to the list, and a removeGestureListener() method for removing them.
  4. Finally, it must provide a processGestureEvent() method, which dispatches a GestureEvent to every listener in its list.
  5. If you have more than one type of Component which can generate GestureEvents, repeat steps 3 and 4 for each Component class.

Does it feel like Swing isn't doing a lot to help you out? That's because it isn't. You are really implementing the entire thing from scratch, and it is purely coincidental that you are naming your classes and methods in a way that is consistent with Swing's naming conventions.

Now consider how you would do this with Buoy:

  1. Create a GestureEvent class to represent a gesture.
  2. There is no step 2.

Buoy takes care of everything else for you. There is no need to define a listener interface. Nor do you need to maintain a list of listeners yourself. Just call addEventLink() on any Widget to add a listener, and dispatchEvent() to send out a GestureEvent to all the registered listeners.

By the way, all of the event handling methods such as addEventLink() and dispatchEvent() are defined by EventSource, which is the superclass of Widget. That means that you can even use Buoy's event handling mechanism for events that do not directly relate to any graphical component. For example, you might have a class which listens on a socket for information coming over a network. You could have it subclass EventSource, then send out NetworkEvent objects when certain types of information are received.

But The Real Reason Is...

I have now given many examples of ways that Buoy's event handling mechanism produces simpler, more easily maintainable code than Swing's. Are you convinced that it is better? Well don't answer yet, because I haven't even started! All of the advantages I have described above are important benefits of using Buoy, but I must admit that none of them was the reason I designed it the way I did.

The true reason for Buoy's event handling mechanism is that it is vastly more powerful than Swing's. It makes it easy, even trivial, to do things that are difficult or impossible with Swing. Let me give a few examples.

Suppose that you want to monitor every event of any sort that is generated by a particular Widget. Simply call

addEventLink(Object.class, this);

and you will get them all. Suppose you then want to "play back" those events at a later time. Simply pass them to the Widget's dispatchEvent() method. This will work even if you have no idea what types of event the Widget might generate, even if some of the events are a custom event class that you do not even know exists.

Doing the same thing with Swing would be an enormous undertaking. You must know in advance every type of event a particular Component may generate. You must implement the event listener interfaces for every one of those event types, and provide a nearly identical implementation of every method defined by every one of those interfaces.

Here is another example. Suppose that a window's contents are variable. Other objects may want to be notified of events being generated by Components inside it, but that list of Components may change at any time.

To do this with Swing, an object that wants to be notified of a particular event type would need to recursively go through the containment hierarchy, identify every Component in the window that can generate that type of event, and add itself as a listener to each one. Any time the contents of the window changed, each listener would need to repeat this process to make sure it is still a listener for every Component. This could possibly be done by listening for HierarchyEvents, or alternatively the window itself could provide a notification mechanism to inform outside objects when its contents have changed.

An alternative solution would be to have the window (or another object) act as a "proxy" for events generated by its contents. The window would add itself as a listener to each Component inside it, then forward events to other objects that wanted to receive them. The window would need to implement an appropriate mechanism for objects to request notification of events generated by its contents, and of course the entire system would need to be implemented separately for every type of event that could be generated. Needless to say, this system will only work if you know in advance every type of event that might be generated by any Component in the window.

With Buoy, proxying events is so trivial that it's almost embarassing. Simply create an EventSource to act as the proxy, then invoke the following method on each of the child Widgets:

widget.addEventLink(Object.class, proxy, "dispatchEvent");

Any object can then call addEventLink() on the proxy, and be notified of any event generated by any Widget in the window. This works even if you have no idea what event types the child Widgets might generate, or which ones an outside listener might be interested in.

One more example. Suppose you want your program's menus to be user configurable. This can be done by using a file to define the list of items in each menu. With either Swing or Buoy, the file can define the name, position, keyboard shortcut, and action command of each menu item. With Buoy, you can take this a step further and actually define what method should be called when the menu item is selected. The is an incredibly powerful technique, especially for programs that can be extended by means of a plugin mechanism.

Yes, But...

I know, even after seeing all the advantages of Buoy's event handling mechanism, something about it still just doesn't seem right. It's so different from how most other Java programs work. Go ahead, let's hear the objections and deal with them.

First of all, isn't it susceptible to errors? Java is a strongly typed language that relies heavily on the compiler to catch mistakes. If you try to call a method that doesn't exist, the compiler will catch it. But suppose you mistype the name of an event handler method:

addEventLink(MousePressedEvent.class, new Object() {
  void procesEvent(MousePressedEvent ev)
  {
    // Do something.
  }
});

There is no way the compiler can realize that the object is supposed to have a method called "processEvent", and you have misspelled it.

This is true. The error will not be caught at compile time, but it will be caught as soon as you run the program. When addEventLink() is called, it will attempt to look up the method, discover it does not exist, and throw an exception.

Now let's consider an equivalent case with Swing:

addMouseListener(new MouseAdapter() {
  public void MousePressed(MouseEvent ev)
  {
    // Do something.
  }
});
This also will compile without problem. Unlike the Buoy case, however, it also will run without complaint. There will be no error message to tell you what you have done wrong. Your event listener will simply never be called, and you can easily waste a lot of time pounding your head against the screen before you finally realize that the method name is capitalized incorrectly. Trust me. I've done it.

OK, but here's another problem. Java supports method overloading. You can't identify a method just by its name. You also need to supply the full list of arguments. How can Buoy deal with overloaded methods, when all it lets you specify is a single String?

To begin with, I should say that I cannot recall a single occasion when I have ever been tempted to overload an event handling method. For example, I do not believe I have ever written a class with two methods both called "actionPerformed", but which took different arguments.

Suppose, however, that you really did want to have two methods with the same name, each of which either takes no argument, or takes a single argument which is compatible with the class passed to addEventLink(). In that case, there is also a form of addEventLink() which takes a Method object instead of a String. I doubt you will ever need it, but it's there in case you do.

Actually, there is one case where this form of addEventLink() might be useful: if you want to use a static method as an event listener, you can pass in the appropriate Method object directly, and pass null for the target object. The static method will then be invoked whenever an event of the desired type occurs. This is completely impossible with Swing, of course, but that doesn't mean it isn't useful.

Custom Widgets

Both Swing and Buoy allow you to define entirely new types of Components/Widgets, but their mechanisms for doing this are somewhat different. To create a custom Swing component, you define a new class which extends JComponent. Your subclass must override paintComponent() to determine the appearance of the Component. Then, if the Component needs to respond to any events, you add appropriate event listeners to it. (Often, the Component itself will be the event listener, but this is not required.)

In Buoy, you use the CustomWidget class to create custom Widgets. Unlike JComponent which is an abstract class, CustomWidget can be instantiated directly. To control the appearance of the Widget, you add an event link to listen for RepaintEvents. There are no behaviors which can only be controlled by overriding methods.

Of course, you will still often want to subclass CustomWidget, and have the subclass manage its appearance and behavior itself. This would be the case when defining a new Widget type that will be reused in many places. On the other hand, if a Widget will only ever appear in one window and is tightly bound to the functionality of that window, it may be more convenient to treat the Widget as a part of the window rather than an independent object. You could then simply create a CustomWidget object, and let the class defining the window take responsibility for painting the Widget and responding to events in it. Buoy gives you the flexibility to use whichever approach is more appropriate to a given situation.

Designing and Building Interfaces

If you survey the hundreds of different GUI toolkits which have been written over the years for use on different platforms, you will find that there are two major approaches for how a program can create its user interface. The first approach is for the program to do everything directly. For every button, menu item, or other graphical object in the interface, there is a piece of source code to create that object and define its properties and relationship to the rest of the interface.

The second approach is to use some form of "user interface definition records" which are stored separately from the program itself. These records define the list of graphical objects in an interface, their properties, their spatial arrangement, and possibly information about their behaviors. When a program wants to create a window, for example, it simply references the record which defines that window.

The second approach is widely accepted as the superior one. Here are just a few of its advantages:

Unfortunately, Swing is tightly bound to the first approach. The assumption that all interfaces will be generated directly by the program is inherent in nearly every aspect of its design. As a result, even though many attempts have been made to build Swing interfaces from external definition records, none of these attempts has achieved more than limited success, and the vast majority of Swing programs still create their interfaces directly in the source code.

In contrast, Buoy has built in support for serializing user interfaces to external files, then reconstructing them again. My intention is that eventually, the "normal" way of writing a Buoy program will be to use a graphical layout tool to create the user interface, save it to a set of interface definition files, and have the program reconstruct the interface directly from these files.

Buoy's serialization mechanism is based on the XMLEncoder class introduced in Java 1.4, but it adds various features both to allow Widgets to be serialized effectively, and to make the mechanism easier to use. To serialize a window or other piece of a user interface, you simply call

WidgetEncoder.writeObject(obj, out);

where obj is the object to save, and out is an output stream to write the XML to. This will save a complete representation of the object, its children, event listeners attached to them, etc. Reconstructing the interface is just as easy:

WidgetDecoder decoder = new WidgetDecoder(in);
BFrame frame = (BFrame) decoder.getRootObject();

WidgetDecoder also allows you to look up any Widget in the file by name (as originally set by calling its setName() method). For example, if the window contains a BTextArea in which the user is asked to enter their address, the following code will look it up:

String address = ((BTextArea) decoder.getObject("address")).getText();

Buoy in Applets

If you want to use Buoy in an unsigned applet, there are a few restrictions you must follow. When I first started to write Buoy, I tested the event handling mechanism by writing an event handler like the following one:

widget.addEventLink(WidgetEvent.class, new Object() {
  public void processEvent(WidgetEvent ev)
  {
    System.out.println(ev.getClass().getName());
  }
});
When Buoy attempted to invoke this event handler using reflection, it threw an IllegalAccessException. This was surprising and distressing. The processEvent() method was public. Why couldn't it be invoked?

A little research turned up the answer. In order to invoke a method by reflection, the invoking code must have permission to access both the method (i.e. the method is declared public), and the class the method belongs to (i.e. it is a public class). By definition, anonymous inner classes are never public, which meant they could never be used as event handlers. This threatened to be a major roadblock, since anonymous inner classes are such a common and convenient way of implementing event listeners.

Fortunately, the problem had an easy solution. The AccessibleObject API allows the ordinary access restrictions to be circumvented when using reflection, so any method may be invoked regardless of permissions. This eliminated the problems with anonymous inner classes, and also had a side benefit which I mentioned earlier: it meant that event handler methods could be declared protected or private.

Unfortunately, this solution is not perfect. Use of the AccessibleObject API may be restricted by a security manager. That means that if you want to use Buoy in an unsigned applet (or most other situations where a security manager is in place), you are bound by two restrictions:

  1. All event handler methods must be public.
  2. All event handler methods must belong to public classes.

As a workaround for this problem, Buoy provides the EventProcessor class which has the following trivial definition:

public abstract class EventProcessor
{
  public void processEvent(Object event)
  {
    handleEvent(event);
  }

  public abstract void handleEvent(Object event);
}

If you want to handle events with an anonymous inner class, have it extend EventProcessor and implement handleEvent():

widget.addEventLink(CommandEvent.class, new EventProcessor() {
  public void handleEvent(Object event)
  {
    // Handle the event
  }
});

Because processEvent() is defined by the public class EventProcessor, Buoy is free to invoke it. processEvent() will then invoke your handleEvent() method. The only disadvantage of this arrangement is that the signature for handleEvent() is fixed by the superclass, so it must always take an argument of type Object rather than a more specific event class.

Remember, this only applies to cases where you are restricted by a security manager. If you are writing an application, a signed applet, or any other type of program which is free from security restrictions, you can ignore this section.

Terms of Use

Buoy 1.9 is hereby released into the public domain. You are free to use it in any way you want for any purpose.