Building Beans

Now that you have the feel from the user’s perspective, let’s build some Beans. In this section we will become the Magic Beans company. We will create some Beans, package them for distribution, and use them in the BeanBox to build a very simple application. (The complete JAR file, along with all of the example code for this chapter, is on the CD-ROM that accompanies this book, and at http://www.oreilly.com/catalog/learnjava.)

The first thing we’ll remind you of is that absolutely anything can be a Bean. Even the following class is a Bean, albeit an invisible one:

public class Trivial implements java.io.Serializable {}

Of course, this Bean isn’t very useful: it doesn’t have any properties, and it doesn’t do anything. But it’s a Bean, nonetheless and you can drag it into the BeanBox. It’s important to realize that JavaBeans really doesn’t give a hard and fast definition for what a Bean is required to be. In practice though, we’ll want Beans that are a little more useful.

Creating a Component with Bindable Properties

We created a nifty Dial component in Chapter 15. What would it take to turn it into a Bean? Well, surprise: it is already a Bean! The Dial has a number of properties that it exposes in the way prescribed by JavaBeans. A "get” method retrieves the value of a property; for example, getValue( ) retrieves the dial’s current value. Likewise, a “set” method (setValue( )) modifies the dial’s value. The dial has two other properties, which also have “get” and “set” methods: minimum and maximum. This is all the Dial needs to do to inform a tool like BeanBox what properties it has and how to work with them. In addition, Dial is a custom Swing component, and if you look, you’ll see that the JComponent class follows the same rules for its important properties (for example, its font).

In order to use our Dial, we put it in a package named magicbeans, and store it in a JAR file that can be loaded by the BeanBox. First, create a directory called magicbeans to hold our Beans, add a package statement to the source files Dial.java, DialEvent.java, and DialListener.java, put the source files into the magicbeans directory, and compile them (javac magicbeans/Dial.java) to create class files.

Next, we need to create a manifest file that tells the BeanBox which of the classes in the JAR file are Beans and which are support files or unrelated. At this point, we only have one Bean, Dial.class, so we create the following file called magicbeans.manifest :

Name: magicbeans/Dial.class 
Java-Bean: True

The Name: label identifies the class file as it will appear in the JAR: magicbeans/Dial.class. Specifications appearing after an item’s Name: line and before an empty line apply to that item. (See the section on JARs and manifest files in Chapter 3, for more details.) We have added the attribute Java-Bean: True, which flags this class as a Bean to tools that read the manifest. We will add an entry like this for each Bean in our package. We don’t need to flag support classes (like DialEvent and DialListener) as Beans, because we won’t want to manipulate them directly with the BeanBox; in fact, we don’t need to mention them in the manifest at all. The jar utility will add appropriate entries for them automatically.

To create the JAR file, including our manifest information, enter the following command:

% jar cvmf magicbeans.manifest magicbeans.jar magicbeans/*.class

Now we can load our JAR into the BeanBox using the Load JAR option under the File menu. Use the File dialog to locate our JAR and select it. An entry for Dial should appear in the Bean palette. We have loaded our first Bean! Drop an instance of Dial Bean into the BeanBox.

As Figure 19.7 shows, the dial’s properties: value, minimum, and maximum are on the properties sheet and can be modified by the BeanBox. (At this point, you’ll see several other properties inherited from the JComponent class as well. This picture shows the Dial Bean as it will appear later in this chapter, after we’ve learned about the BeanInfo class). We’re almost there. But these properties are not very useful to other Beans unless we can notify them when the dial’s value changes. We need to make value a bound property, by firing PropertyChangeEvent s when the value changes. (Alternately, we could have other beans that know about DialEvents. It really depends on how we plan to design our suite of beans.) It won’t be hard to add PropertyChangeEvents to our Bean because the JComponent class already handles them for us.

The Dial component as a Bean

Figure 19-7. The Dial component as a Bean

We insert the firePropertyChange statement as the first line of the Dial’s setvalue( ) method:

public void setValue(int value) {
    firePropertyChange( "value", this.value, value );
    ...

That’s all it takes to make Dial a source of PropertyChangeEvents. To fire an event, we use the firePropertyChange( ) method built into JComponent. The JComponent also handles all of the listener registration for us.

The firePropertyChange( ) method takes three arguments: the name of the property and the old and new values. It may seem superfluous to send both the old and new values, but there is one bonus when we do: the firePropertyChange( ) method doesn’t generate an event if the value has not actually changed. This saves us the trouble of implementing “event-avoidance” logic over and over. (It also prevents looping and other bad behavior.) Various overloaded versions of firePropertyChange( ) in the JComponent class accept different argument types, including Object and all of the primitive numeric types.

Now we’re ready to put the Dial to use. Recompile and re-JAR the classes. Next, reload the Juggler example that we asked you to save in the first section of this chapter. (Did you save it?) Add an instance of our new magic Dial Bean to the scenario, as shown in Figure 19.8.

The Juggler with a dialable animation rate

Figure 19-8. The Juggler with a dialable animation rate

Let’s try to bind the value property of the Dial to the animationRate of the Juggler. The Bind Property option should now be available under the Edit menu because the BeanBox recognizes that we are a source of PropertyChangeEvents. (Note that there are a lot of properties listed here. Again, we’ll show you later how to limit what is presented.) When you complete the hookup, you should be able to vary the speed of the juggler by turning the dial. Try changing the maximum and minimum values of the dial to change the range.

Design patterns for properties

We said earlier that tools like BeanBox found out about a Bean’s properties by looking at its get and set methods. The easiest way to make properties visible is to follow these simple design patterns:

  • Method for getting the current value of a property:

public propertyType getPropertyName( )
  • Method for setting the value of a property:

public void setPropertyName( propertyType arg )
  • Method for determining whether a boolean-valued property is currently true:

public boolean isPropertyName( )

The last method is used only for properties with boolean values, and is optional. (You could just use the get method in this situation.)

The appropriate set and get methods for these features of our Bean are already in the Dial class, either methods that we added or methods inherited from the java.awt.Component and javax.swing.JComponent classes:

// inherited from Component 
public Color getForeground( )  
public void setForeground(Color c)  
 
public Color getBackground( )  
public void setBackground(Color c)  
 
public Font getFont( ) 
public void setFont(Font f)  
 
// many others from Component and JComponent

// part of the Dial itself 
public int getValue( ) 
public void setValue(int v) 
 
public int getMinimum( ) 
public void setMinimum(int m) 
 
public int getMaximum( ) 
public void setMaximum(int m)

BeanBox uses the reflection API to find out about the Dial Bean’s methods (both its own and the methods it inherits); it then uses the design patterns to figure out what properties are available. When we use the properties sheet to change a value, the BeanBox dynamically invokes the correct set method to change the value.

But wait—if you look further at the JComponent class, you’ll notice that other methods match the design pattern. For example, what about the setCursor( ) and getCursor( ) pair? BeanBox doesn’t know how to display or edit a cursor, so it simply ignores those properties in the properties sheet.

BeanBox automatically pulls the property’s name from the name of its accessor methods; it then lowercases the name for display on the properties sheet. For example, the font property is not listed as Font. Later, we’ll show how to provide a BeanInfo class that overrides the way these properties are displayed, letting you provide your own friendly property names.

JavaBeans allows read-only and write-only properties, which are implemented simply by leaving out the get or set method.

A (Slightly) More Realistic Example

We now have one nifty Bean for the Magic Beans products list. Let’s round out the set before we start advertising. Our goal is to build the Beans we need to make a very simple form. The application will perform a simple calculation after data is entered on the form.

A Bean for displaying text

One thing that we will need in almost any application is a plain old text label. Fortunately, Swing provides us with one for free.

We make a trivial subclass and package it as a Bean:

//file: TextLabel.java
package magicbeans; 
import javax.swing.JLabel;

public class TextLabel extends JLabel {

    public void setText( String s ) {
        super.setText(s);

        if ( getParent( ) != null ) {
            invalidate( );
            getParent().validate( );
        }
    }
}

The only thing we’ve added here is a bit of code to make the JLabel behave better in the BeanBox. We override the setText( ) method to have it revalidate its container whenever the label changes. This will cause the label to snap to the correct size automatically when you change the text in the property sheet.

Recreate the JAR file and try out the label in the BeanBox. Don’t forget to add TextLabel.class to the manifest and to specify that it’s a Bean.

A Bean for validating numeric data

Another component that we’re sure to need in a form is a text field that accepts numeric data. Let’s build a text-entry Bean that accepts and validates numbers and makes the values available as a property. You should recognize almost all of the parts of the NumericField Bean:

//file: NumericField.java
package magicbeans;
import javax.swing.JTextField;
import java.awt.event.*;
import java.beans.*;

public class NumericField extends JTextField
                          implements ActionListener {
    private double value;

    public NumericField( ) { 
        super(6);
        addActionListener( this );
    }

    public void actionPerformed( ActionEvent e ) {
        try { 
            setValue( Double.parseDouble( getText( ) ) );
        } catch ( NumberFormatException ex ) { 
            select(0, getText().length( ));
        }
    }

    public double getValue( ) {
        return value;
    }

    public void setValue( double newValue ) {
        double oldValue = value;
        value = newValue;
        setText( "" + newValue );
        firePropertyChange( "value", oldValue, newValue );
    }
}

NumericField extends the Swing JTextField component. The heart of NumericField is in the actionPerformed( ) method. You’ll recall that a JTextField generates ActionEvents whenever the user presses Return to enter the data. We catch those events and try to parse the user’s entry as a number, giving it a Double value. If we succeed, we update the value property using our setValue( ) method. setValue( ) then fires a PropertyChangeEvent to notify any interested Beans. This event firing enables us to bind NumericField’s value property to some other property. (We’ll see how in the next example.)

If the text doesn’t parse properly as a number, we give feedback to the user by selecting (highlighting) the text. The field defaults to a width of six columns, but you can change its size by dragging it.

Verify the operation of NumericField by placing two of them in the BeanBox and binding the value property of one to the other. You should be able to enter a new floating point value and see the change reflected in the other.

An invisible multiplier

Finally, let’s try our hand at an invisible Bean: one that performs a calculation rather than providing part of a user interface. Multiplier is a simple invisible Bean that multiplies the values of two of its properties (A and B) to produce the value of a third read-only property (C). Here’s the code:

//file: Multiplier.java
package magicbeans; 
import java.beans.*; 
 
public class Multiplier implements java.io.Serializable { 
  private double a, b, c; 
   
  synchronized public void setA( double val ) {  
    a = val;  
    multiply( ); 
  } 

  synchronized public double getA( ) {  
    return a;  
  } 

  synchronized public void setB( double val ) {  
    b = val;  
    multiply( ); 
  } 

  synchronized public double getB( ) {  
    return b;  
  } 

  synchronized public double getC( ) {  
    return c;  
  }  

  private void multiply( ) { 
    double oldC = c; 
    c = a * b; 
    propChanges.firePropertyChange(
      "c", new Double(oldC), new Double(c)); 
  } 
 
  private PropertyChangeSupport propChanges =
      new PropertyChangeSupport(this); 
 
  public void
  addPropertyChangeListener(PropertyChangeListener listener) { 
    propChanges.addPropertyChangeListener(listener); 
  } 

  public void
  removePropertyChangeListener(PropertyChangeListener listener) { 
    propChanges.removePropertyChangeListener(listener); 
  } 
}

Because a Multiplier is invisible, it doesn’t extend the JComponent class. To make a Multiplier a source of PropertyChangeEvents, we enlist the help of a PropertyChangeSupport object. When we need to invoke Multiplier’s methods for registering property-change listeners, we simply call the corresponding methods in the PropertyChangeSupport object. Similarly, a Multiplier fires a property change event by calling the PropertyChangeSupport object’s firePropertyChange( ) method. This is the easiest way to get an arbitrary class to be a source of PropertyChangeEvents.

The code is straightforward. Whenever the value of property A or B changes, we call multiply( ), which multiplies their values and fires a PropertyChangeEvent. So we can say that Multiplier supports binding of any of its properties.

Putting them together

Finally, let’s demonstrate that we can put our Beans together in a useful way. Arrange three TextLabel s, three NumericFields, and a Multiplier into the scene shown in Figure 19.9.

TextLabels, NumericFields, and a Multiplier

Figure 19-9. TextLabels, NumericFields, and a Multiplier

Bind the values of the first two NumericFields to the A and B properties of the Multiplier; bind the C value to the third NumericField. Now we have a simple calculator. You could use this as a tip calculator, but it’s important to realize that much more is possible. Try some other arrangements. Can you build a calculator that squares a number? Can you see how you might build a simple spreadsheet?

Before moving on, save this work so that you can reuse it later. This time, use the BeanBox’s Serialize component command to serialize the BeanBox container itself. To select the top-level BeanBox, click on the background of the workspace. The dashed line should appear around the entire BeanBox. Then use the Serialize component command to save your work. By serializing the BeanBox container, we save all of the Beans it contains and all of their interconnections. Later in this chapter, we’ll show you how to put these to use. (BeanBox’s Save command also stores the state of the BeanBox, but it may or may not use serialization to do so.)

Customizing with BeanInfo

So far, everything the BeanBox has known about our Beans has been determined by low-level reflection—that is, by looking at the methods of our classes. The java.beans.Introspector gathers information on a Bean using reflection, then analyzes and describes a Bean to any tool that wants to know about it. The introspection process works only if we follow design patterns that restrict what we call our methods; furthermore, it gives us little control over exactly what properties and events appear in the BeanBox menus. We have been forced to live with all of the stuff that we inherit from the base Swing components, for example. We can change all that by creating BeanInfo classes for our Beans. A BeanInfo class provides BeanBox’s introspector with explicit information about the properties, methods, and events of a Bean; we can even use it to customize the text that appears in the BeanBox’s menus.

A BeanInfo class implements the BeanInfo interface. That’s a complicated proposition; in most situations, the introspector’s default behavior is reasonable. So instead of implementing the BeanInfo interface, we extend the SimpleBeanInfo class, which implements all of BeanInfo’s methods. We can override specific methods to provide the information we want; when we don’t override a method, we’ll get the introspector’s default behavior.

In the next few sections, we’ll develop a DialBeanInfo class that provides explicit information about our Dial Bean.

Getting Properties information

We’ll start out by describing the Dial’s properties. To do so, we must implement the getPropertyDescriptors( ) method. This method simply returns an array of PropertyDescriptor objects—one for each property we want to publicize.

To create a PropertyDescriptor , we call its constructor with two arguments: the property’s name and the class. In the following code, we create descriptors for the Dial’s value, minimum, and maximum properties. Then we call a few methods of the PropertyDescriptor class to provide additional information about each property. In this example, we call the setBound( ) method to state that minimum and maximum are not bound properties but that value is a bound property. Our code also is prepared to catch an IntrospectionException, which can occur if something goes wrong while creating the property descriptors:

//file: DialBeanInfo.java
package magicbeans; 
import java.beans.*; 
 
public class DialBeanInfo extends SimpleBeanInfo { 
 
  public PropertyDescriptor[] getPropertyDescriptors( ) { 
    try { 
      PropertyDescriptor value =  
        new PropertyDescriptor("value", Dial.class); 
      PropertyDescriptor minimum =  
        new PropertyDescriptor("minimum", Dial.class); 
      PropertyDescriptor maximum =  
        new PropertyDescriptor("maximum", Dial.class); 
       
      value.setBound(true); 
      minimum.setBound(false); 
      maximum.setBound(false); 
 
      return new PropertyDescriptor [] { value, minimum, maximum }; 
    }
    catch (IntrospectionException e) { 
      return null;  
    } 
  }
}

Perhaps the most interesting thing about DialBeanInfo is that by providing explicit information for our properties, we automatically hide any other properties that introspection might find. If you don’t provide any property information, a development tool like BeanBox will find out about all sorts of properties, including properties inherited from the superclass. When we loaded the Dial into the BeanBox, we saw a number of properties inherited from Component. If you compile DialBeanInfo, package it with the Dial, and load the resulting JAR file into the BeanBox, you’ll see that the Component properties no longer appear in the properties sheet.

A PropertyDescriptor can provide a lot of other information about a property: it can provide the names of the accessor methods (if you decide not to use the design patterns); whether the property is constrained; and a class to use as a property editor (if the standard property editors aren’t sufficient).

Getting Events information

The Dial defines its own event: the DialEvent. We’d like to tell development tools about this event, so that we can build applications using it. The process for telling the world about our event is similar to what we did previously: we add a method to the DialBeanInfo class called getEventSetDescriptors( ) , which returns an array of EventSetDescriptor s.

Events are described in terms of their listener interfaces, not in terms of the event classes themselves, so our getEventSetDescriptors( ) method creates a descriptor for the DialListener interface. We also have to tell the world that we generate PropertyChangeEvents, so we create a descriptor for the PropertyChangeListener. Here’s the code to add to the DialBeanInfo class:

public EventSetDescriptor[] getEventSetDescriptors( ) { 
  try { 
    EventSetDescriptor dial = new EventSetDescriptor(
      Dial.class, "dialAdjusted",
      DialListener.class, "dialAdjusted");
    dial.setDisplayName("Dial Adjusted"); 
 
    EventSetDescriptor changed = new EventSetDescriptor(
      Dial.class, "propertyChange",
      PropertyChangeListener.class, "propertyChange");
    changed.setDisplayName("Bound property change");

    return new EventSetDescriptor [] { dial, changed };
  }
  catch (IntrospectionException e) { 
    return null;
  } 
}

In this method, we create two EventSetDescriptor objects: dial and changed. The constructor for an EventSetDescriptor takes four arguments: the class that generates the event, the name of the event (the name that will be displayed, by default, by a development tool), the listener class, and the name of the method to which the event can be delivered. (Other constructors let you deal with listener interfaces that have several methods.) After creating these objects, we call the setDisplayName( ) method to provide a more friendly name to be displayed by development tools like the BeanBox. (This overrides the default name specified in the constructor.)

Just as the property descriptors we supply hide the properties that were discovered by reflection, the EventSetDescriptors hide the other events that are inherited from the base component classes. Therefore, when you recompile DialBeanInfo, package it in a JAR, and load it into the BeanBox, you’ll no longer see mouse events, action events, and all the other AWT/Swing events. You will see only the two events that we have explicitly described: our own DialEvent and PropertyChangeEvent (displayed as “Dial Adjusted” and “Bound property change”).

Once you have an EventSetDescriptor, you can provide other kinds of information about the event. In particular, you can state that the event is unicast, which means that it can only have one listener.

Supplying icons

Some of the Beans that come with the BeanBox are displayed on the palette with a cute icon. This makes life more pleasant for everyone. To supply an icon for the BeanInfo object we have been developing, we have it implement the getIcon( ) method. You may supply as many as four icons: they may be either 16 × 16 or 32 × 32, and either color or monochrome. Here’s the getIcon( ) method for DialBeanInfo:

public class DialBeanInfo extends SimpleBeanInfo { 
  ... 
  public java.awt.Image getIcon(int iconKind) { 
 
    if (iconKind == BeanInfo.ICON_COLOR_16x16) { 
      return loadImage("DialIconColor16.gif"); 
    } else 
    if (iconKind == BeanInfo.ICON_COLOR_32x32) { 
      return loadImage("DialIconColor32.gif"); 
    } else 
    if (iconKind == BeanInfo.ICON_MONO_16x16) { 
      return loadImage("DialIconMono16.gif"); 
    } else 
    if (iconKind == BeanInfo.ICON_MONO_32x32) { 
      return loadImage("DialIconMono32.gif"); 
    } 
    return null; 
  }

This method is called with a constant indicating what kind of icon is being requested; for example, BeanInfo.ICON_COLOR_16x16 requests a 16 × 16 color image. If an appropriate icon is available, it loads the image and returns an Image object. If the icon isn’t available, it returns null. For convenience, you can package the images in the same JAR file as the Bean and its BeanInfo class.

Though we haven’t used them here, you can also use a BeanInfo object to provide information about other public methods of your Bean, array-valued properties, and other features.

Creating customizers and property editors

JavaBeans also lets you provide a customizer for your Beans. Customizers are objects that do advanced customization for a Bean as a whole; they let you provide your own GUI for tweaking your Bean. (For example, the Select Bean uses a customizer rather than the standard properties sheet.) We won’t show you how to write a customizer; it’s not too difficult, but it’s beyond the scope of this chapter. Suffice it to say that a customizer must implement the java.beans.Customizer interface, and should extend Component (or JComponent), so that it can be displayed.

A property editor isn’t quite as fancy as a customizer. Property editors are a way of giving the properties sheet additional capabilities. For example, you would supply a property editor to let you edit a property type that is specific to your Bean. You could provide a property editor that would let you edit an object’s price in dollars and cents. We’ve already seen a couple of property editors: the editor used for Color-valued properties is fundamentally no different from a property editor you might write yourself. In addition, the Molecule Bean used a property editor to specify its moleculeName property.

Again, describing how to write a property editor is beyond the scope of this chapter. Briefly, a property editor must implement the PropertyEditor interface; it usually does so by extending the PropertyEditorSupport class, which provides default implementations for most of the methods.

Get Learning Java now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.