Building a bevy of beans: Create reusable JavaBeans components

With the basics of beans in the bag, you're now ready to see how easy it is to develop more advanced beans

In this short series, we are examining the development of JavaBeans software components. Ultimately, most beans will be manipulated in a beans development environment; however, we are only concerned here with the source-level aspects of the framework. The advantages of developing JavaBeans -- that is, developing to the JavaBeans specification -- are severalfold, among them:

  • Beans can be easily manipulated in visual development environments by users who need not be technically skilled in source-level Java development.

  • Because of the standard interface, beans are readily distributable, which allows third-party components to be more easily integrated into development efforts.

  • Developers can easily transfer code that has been developed for one project into a reusable library of components, which can be accessed in future development efforts.

The eye of the storm

In the

first part of this series

, we developed two simple beans: a non-visual alarm bean and a graphical left-arrow/right-arrow bean. Both were augmented with visual

customizer

and

bean information

classes. In the beans we cover this month, we won't be providing customizers; instead, we'll concentrate on using existing beans and components to create bigger, better beans.

Prerequisites

As the continuation of a two-part series, I will assume familiarity with the issues discussed in the previous installment, including the supplementary articles and resources.

The beans

From start to finish of this series, we develop the following beans:

AlarmBean
AlarmBean
A non-graphical bean that fires off an event after a specified delay.
ArrowBean
ArrowBean

A graphical left-arrow/right-arrow bean.

ProgressBean

Prog ressBean

A graphical progress-display bean.

NumberFieldBean

Nu mberFieldBean

A graphical numeric TextField bean with roll buttons. This bean makes use of the ArrowBean bean.

FontChooserBean

FontChooserBean

A graphical font-chooser bean. This bean makes use of the NumberFieldBean bean.

FontSelectorBean

FontSelectorBean

A graphical font-chooser bean that displays the current font and provides OK/Cancel buttons. This bean makes use of the FontChooserBean bean.

FontDialogBean

FontDialogBean

A graphical font-chooser bean that pops up the font selector in a separate dialog. This bean makes use of the FontSelectorBean bean.

We discussed the AlarmBean and ArrowBean beans in detail last month; in this episode, we will discuss the remaining beans in varying levels of detail.

You may be wondering why we're building three font beans. The ultimate goal is simply to produce a font selector bean that pops up a font dialog when the user clicks on a button. This task very naturally divides into the three beans we'll produce: The first is the user interface for the font selection, the second adds dialog controls and a font sample, and the third introduces a button to pop up the dialog and contains the basic dialog-handling code.

Without beans, we would have to develop these items as specialized AWT components or as a single monolithic class; using beans, we can develop the three parts as independent beans that are reusable in their own right.

Our scope

As with the first installment of this series, we are only concerned with the beanisms of these classes and not the actual nuts and bolts that make them tick. As a result, we will discuss the beans in skeletal form, highlighting in red the fragments that are of particular relevance, and leaving the other details for you to peruse in your spare time. Neither will we concern ourselves with the customizers, which we covered in sufficient detail with our discussion of the first two beans.

To see the forced labor behind the beans, check out the complete source code.

Building the ProgressBean bean

ProgressBean

is a simple progress display bean. It is a custom AWT component that displays a percentage value and graphical bar-representation of this value, as shown in the figure below. It exposes two properties: the current and maximum bar values.

The ProgressBean bean

The current value is exposed as an observable property. Observable properties are properties whose changes can be observed. Observers are registered with the bean in the same way that event listeners are, and they are notified whenever a property changes. Individual properties of a bean must be made explicitly observable by the bean; it is not possible to observe changes to just any property of any bean.

This bean is implemented with the following two classes:

Class ProgressBean

The

ProgressBean class is the main bean class, a simple custom AWT component and Java bean.

public class ProgressBean extends Component ...

This bean is a lightweight component, so we extend Component instead of Canvas, and provide an appropriate paint() method. The lightweight component framework is more efficient than the traditional custom-component framework, requiring fewer resources of the local windowing system. As a component, we automatically inherit the serializability mandated by JavaBeans, and we provide the default no-arg constructor.

public void setBarground (Color c) ...
public Color getBarground () ...
public synchronized void setMaximum (int m) ...
public int getMaximum () ...

Here, we expose the Color property barground (the color of the displayed bar) and the int property maximum (the maximum bar value).

public synchronized void setValue (int v) {
  if (value != v) {
    value = v;
    repaint ();
    fireValueChange ();
  }
}
public int getValue () ...

The int property value is observable, which means that we must inform all interested listeners whenever its value changes. To this end, we call our fireValueChange() method to inform the listeners whenever setValue() is called.

protected PropertyChangeSupport listeners = new PropertyChangeSupport (this);
public void addPropertyChangeListener (PropertyChangeListener l) {
  listeners.addPropertyChangeListener (l);
}
public void removePropertyChangeListener (PropertyChangeListener l) {
  listeners.removePropertyChangeListener (l);
}

Here, we maintain a list of objects that are registered to be notified whenever an observable property changes. We use the class PropertyChangeSupport from the java.beans package to maintain this list. The constructor for this class requires us to specify the bean that will be the origin of property change events; in this case, it is this, and the methods that it provides allow us to maintain the list.

By exposing the methods addPropertyChangeListener() and removePropertyChangeListener(), we automatically indicate that this bean has observable properties. We do not, however, indicate which properties are observable. That information must be appropriately documented with the bean.

protected Integer oValue = new Integer (value);
protected void fireValueChange () {
  listeners.firePropertyChange ("value", oValue, oValue = new Integer (value));
}

We call this method to notify listeners of a change in our value property; we use the firePropertyChange() method of our list to propagate this notification. The first parameter is the name of the property, which should match the name of an exposed property; the second parameter is the old value of the property; and the third property is the new value. The PropertyChangeSupport class returns without doing anything if the old and new values are the same.

Class ProgressBeanBeanInfo

The

ProgressBeanBeanInfo class simply describes the ProgressBean bean, obscuring any inherited information that we wish to obscure.

Building the NumberFieldBean bean

This bean implements a common user-interface component, the rollable number entry field -- a numeric text field that provides increment and decrement arrows, as shown in the figure below. This bean brings up an important JavaBeans concept:

programmatic manipulation of beans

.

The NumberFieldBean bean

Programmatic manipulation of beans refers to the mechanisms that JavaBeans provides for programmatically creating and accessing beans. Although it is possible to access beans using the standard Java object creation (new X ()) and type-casting mechanisms ((Y) x), it is recommended that you use the provided JavaBeans mechanisms to allow for future extension of the JavaBeans framework.

This bean is implemented with the following two classes:

Class NumberFieldBean

The NumberFieldBean class, the main bean class, is an AWT container that adds three components: two ArrowBean beans and a TextField. Programmatic access to the ArrowBean class requires that we make use of the bean manipulation mechanisms I mentioned a moment ago.

The current numeric value is exposed as an observable property. Although it is a normal property that can be accessed and manipulated through the usual beans accessor methods, it is also observable, so listeners can register to be notified whenever its value changes. We do not fire an event when the user presses Return, although that would be an obvious extension to this class.

public class NumberFieldBean extends Container implements ActionListener ...

We extend Container and implement ActionListener in order to receive events from the beans and AWT components that we use. Extending Container instead of the more traditional Panel means that this bean, like the ProgressBean bean is a lightweight component.

public NumberFieldBean () ...

As a bean, we must provide a public no-arg constructor. Note that we should not provide other constructors for programmatic use; doing so would go against the JavaBeans access mechanism.

try {
  down = (ArrowBean) Beans.instantiate (getClass ().getClassLoader (), "org.merlin.beans.arrow.ArrowBean");
} catch (Exception ex) {
  ex.printStackTrace ();
}

Here, we create an ArrowBean using the programmatic beans instantiation mechanism. We don't use the standard Java new operator; instead, we use the instantiate() method of class Beans. We specify the ClassLoader to use for loading the bean class; in this case, we use our own ClassLoader and the fully qualified name of the bean class ("org.merlin.beans.arrow.ArrowBean"), and cast the resulting Object to the appropriate class.

Note that the instantiate() method may throw a variety of exceptions (for example, if the specified bean could not be located). We simply catch and display any such exceptions, which, by the way, should not occur if the bean is appropriately installed.

add ("East", (Component) Beans.getInstanceOf (down, Component.class));

Here, we cast the ArrowBean to a Component and add it as a normal Component. We don't use the standard (Component) type-casting mechanism, and we don't use the fact that our AlarmBean is a subclass of Component; instead, we use the getInstanceOf() method of class Beans. We specify the bean that we wish to cast and the Class object to which we wish to cast it (in this case, Component.class).

Although this approach makes little sense right now, future versions of JavaBeans will support beans composed of multiple class files, as well as beans that can expose different aspects of themselves as the different classes. For example, a bean could appear to subclass both Component and RemoteObject by providing two coupled classes: a Component and a RemoteObject. Using the JavaBeans type-casting mechanism, the appropriate bean object can be returned automatically, so beans can have apparent multiple-inheritance, although Java does not natively support this. For details, see the "Glasgow" JavaBeans specification. (A link to this spec is provided in the Resources section of this article.)

It is necessary for us to use these beans access mechanisms now, so we can transition our beans to future JavaBeans technologies without any problems.

down.setDirection (ArrowBean.LEFT);
down.addActionListener (this);

Here, we configure the ArrowBean using the setDirection() property accessor and the addActionListener() registration method. We can use these property accessors and listener registration methods directly on the bean we just created; it is only necessary to use the JavaBeans type-casting feature when we are accessing an aspect of a bean that is inherited from another class.

public synchronized void setValue (int v) {
  field.setText (String.valueOf (v));
  fireValueChange (getValue ());
}
public synchronized int getValue () ...

Here, we expose the int property value, which is the value of this field. This property is observable, so we must notify listeners whenever it is changed. We do this by calling our fireValueChange() method.

public void setColumns (int c) ...
public int getColumns () ...
public synchronized void setMinimum (int m) ...
public int getMinimum () ...
public synchronized void setMaximum (int m) ...
public int getMaximum () ...
public synchronized void setStep (int s) ...
public int getStep () ...

Here, we expose the int properties columns, minimum, maximum, and step, which are, respectively, the number of columns displayed in the TextField, the minimum and maximum values that this field should hold, and the amount by which the arrow buttons should alter the value. These properties are not observable.

Note that we use synchronization to ensure thread safety where appropriate.

public synchronized void actionPerformed (ActionEvent e) {
  int value = getValue ();
  if (e.getSource () == down) {
    if (value > minimum) {
      value = (value - step > value) ? minimum : clamp (value - step);
      setValue (value);
    }
  } ...
1 2 3 Page 1
Page 1 of 3