by Mark Johnson

Turn Java classes into JavaBeans

how-to
Jun 1, 199818 mins

Reuse your existing Java classes as JavaBeans components

Like many Java programmers, you’ve invested a lot of time and effort into your existing Java classes. You feel rightly proud of what you’ve accomplished. True, some of your classes may have been written for JDK 1.0.2, or even earlier, but they still run just fine, thank you very much. Wouldn’t it be great if you could dust off those old applets and classes and repackage them as shiny new JavaBeans? Well, the good news is, you can. While some of the JavaBeans Specification depends on JDK 1.1 functionality, class files written to JDK 1.0.2 can still be modified to run as beans.

Beanify your applet

We’re going to learn to “beanify” an applet by example. The applet we’re going to beanify, ColorFadeBar, produces a title bar (perhaps for a Web page), with a color gradient, like this:

You can see the full source code for this applet here. You may want to open another browser window (or print the applet) so you can follow along with this discussion.

Task 1: Identify your properties

The first task in beanifying an existing class (whether that class is an applet or not) is to identify what attributes of the class can be considered properties. (See my previous article on customization in the Resources below.) Properties allow the user of a class to customize how the class appears or behaves. For example, Figure 2 shows some attributes of the ColorFadeBar applet that are programmable (via the <PARAM> tags within an <APPLET> tag in HTML).

Figure 2: Programmable attributes of a ColorFadeBar

The font, font size, color, and alignment of the text message are all parameterized, as are the beginning and ending colors for the cool background fade effect, and the width and height of the applet. There are also two parameters (X0 and DY, not shown here; see Table 1 below) that allow you to nudge the text vertically or horizontally for various purposes.

These attributes of the applet aren’t properties yet. A JavaBeans property consists of methods that allow the retrieval and/or modification of some internal state of a class. The applet currently doesn’t have methods that provide that functionality.

Although applet parameters are often directly translatable to properties (since their purpose usually is to affect the appearance or behavior of an object), they are fundamentally different mechanisms. An applet accesses a parameter by calling java.applet.Applet.getParameter(), and then converting whatever string is returned to the appropriate type. A property, on the other hand, consists of a method or methods that the class exposes (that is, makes publicly available) in order to allow external agents to access encapsulated information.

If you used parameters while writing your applet, you were probably already thinking in terms of properties. In fact, the parameters to your applet are your first candidates for the properties of your new bean. Our ColorFadeBar applet has the following parameters:

ParameterMeaning
WIDTHThe width of the applet
HEIGHTThe height of the applet
STARTCOLORThe beginning color for the background fade
ENDCOLORThe ending color for the background fade
TEXTCOLORThe color in which to render the message
TEXTThe message text
FADEDIRBackground fade direction (left, right, up, down)
TEXTDIRText alignment (left, center, right)
FONTThe name of the font for TEXT
FONTSIZEThe size of the font for TEXT
X0

Text left or right margin offset from applet edge

(depending on TEXTDIR; default 5)

DYText baseline offset (default 0)
Table 1. Parameters to existing applet

These parameters can be used to customize how the applet works. We first need to identify what the properties of the new bean will be, and then add accessor functions for them. Table 2 shows the properties I’ve chosen for the new bean, a description of each property, and the parameter or parameters to which the property corresponds.

Property nameTypeDescriptionParameter(s)
HeightintHeight of the appletHEIGHT
WidthintWidth of the appletWIDTH
MessageStringThe message to displayTEXT
MessageFontFontThe font of the messageFONT, FONTSIZE
TextDirectionintThe text alignmentTEXTDIR
MessageColorColorThe color of the textTEXTCOLOR
ColorFromColorThe color at which fade beginsSTARTCOLOR
ColorToColorThe color at which fade endsENDCOLOR
FadeDirectionintThe direction in which color fadesFADEDIR
Table 2. Properties of the new JavaBean

Notice that some of the property names differ from their corresponding parameter names. There is no programmatic relationship between parameters and properties. In practice, it’s probably best to give properties the same name as their corresponding parameters, but here I’ve changed them to demonstrate that they’re entirely independent from parameters. Also, notice that one new property we’re going to add, MessageFont, corresponds to more than one applet parameter. Another difference between parameters and properties is that applet parameters are all strings. It’s frequently necessary to convert many of these properties from their string representations to some other type — for example (in the ColorFadeBar source code):

    protected int ixGetParameter(String sName, int iDefault)
    {
        int i;
        try {
       // Conversion from string to int occurs here
            i = Integer.parseInt(getParameter(sName), 16);
        }
        catch (NumberFormatException e) { i = iDefault; }
        return i;
    }
...
       // Conversion from int to Color occurs here
        if ((i = ixGetParameter("TEXTCOLOR", 0xffffff)) >= 0)
            _colorText = new Color(i);

In the JavaBean we’re about to write, the MessageColor property will be accessible as type Color: No translation is necessary!

If the class you’re turning into a bean isn’t an applet (and therefore doesn’t have parameters), don’t worry. You can still examine the class and imagine what attributes you’d like to be able to customize if the class were a bean. For example, a class that accesses a database and returns a list of tables in the database is much more reusable if the name and location of the database can be configured by calling property accessors.

Once you’ve identified what properties your bean should have, the next step is to actually create the properties.

Task 2: Create bean properties

Now that we’ve identified the properties of our new bean (we’ve begun to define the Application Programming Interface, or API), we need to write code that makes the class a bean.

Here we come to a fundamental decision point. There are two options: Either we modify the existing class, which is possible if we have the source code, or we subclass the existing class (or applet) and add Bean functionality that way. I’ve taken the second approach for a few reasons:

  • Compatibility. Many applets are only Java 1.0.x compatible. Modifying the class to use Java 1.1+ features can cause problems if the applet is in use elsewhere. If you’re operating in a totally Java-1.1 environment where you’re sure your classes are operating in a Java 1.1 interpreter, compatibility probably won’t be an issue for you.

  • Clarity. It’s much easier to see what functionality is bean-related if you encapsulate all of the bean functionality in a separate subclass. You may or may not care about this for your application.

  • Availability of source code. If source code is unavailable, subclassing is your only alternative. There’s a problem though: If you don’t have the source code, you may not know the names of the protected variables you might need to make the set/get methods work. In this case, you might try a decompiler (assuming, of course, that you’re not violating any license agreements) and guessing at what the field names mean, or experimenting with the decompiled code.

So, now we’ve decided to subclass ColorFadeBar to create the new ColorFadeBean. We create properties by adding accessor methods for each of the properties. Note that we’ve followed the naming conventions (unfortunately called design patterns by Sun) by naming property accessors void setProperty(Type param) and Type getProperty(). (That naming conventions are called design patterns by Sun is unfortunate because the phrase has an entirely different meaning elsewhere in the industry.) The Introspector class looks for signatures of this form in order to automatically identify properties of the class.

The property accessor methods that we’ve added to the class appear below:

    // Width property
    public void setWidth(int iWidth) { _iHpoints = iWidth;}
    public int getWidth() { return _iHpoints;}
    // Height property
    public void setHeight(int iHeight) { _iVpoints = iHeight; }
    public int getHeight() { return _iVpoints; }
    // Message property
    public void setMessage(String sText) {        _sString = sText;    }
    public String getMessage() { return _sString; }
    // Color From property
    public void setColorFrom(Color newColor) { _colorFrom = newColor;}
    public Color getColorFrom() { return _colorFrom;}
    // ColorTo property
    public void setColorTo(Color newColor) { _colorTo = newColor;}
    public Color getColorTo() { return _colorTo;}
    // Message font property
    public void setMessageFont(Font font) {
        if (font == null) { font = new Font("Helvetica", Font.BOLD, 12); }
        _messageFont = font;
    }
    public Font getMessageFont() { return _messageFont;}
    // Message color
    public void setMessageColor(Color color) { _colorText = color;}
    public Color getMessageColor() { return _colorText;}
    // Fade direction
    public void setFadeDirection(int iNewFadeDirection)
    {
        // If new direction is out of range, ignore
        if (iNewFadeDirection >= 0 || iNewFadeDirection <= 4) {
            _iFadeDirection = iNewFadeDirection;
            repaint();
        }
    }
    public int getFadeDirection() { return _iFadeDirection;}
    // Text direction
    public void setTextDirection(int iNewTextDirection)
    {
        // If new direction is out of range, throw
        // exception
        if (iNewTextDirection == LEFT || iNewTextDirection == RIGHT ||
            iNewTextDirection == CENTER) {
            _iTextDirection = iNewTextDirection;
            repaint();
        }
    }
    public int getTextDirection() { return _iTextDirection;}
    // Handle real-time resizes.
    public void setBounds(int x, int y, int width, int height) {
        super.setBounds(x,y,width,height);
        setWidth(width);
        setHeight(height);
    }

You see we’ve also added setBounds(), which allows the application developer to resize the applet visually. Figure 3 shows the ColorBarBean operating as a bean in the BeanBox. Notice that all of the properties we’ve defined appear as expected in the property sheet to the right.

Task 3: Add custom property editors

Our JavaBean already looks good, but we want to make it easier to use for the application developer. One way to do this is by adding custom property editors that present properties in a more easily readable fashion. (See my previous article on property editors in the Resources.)

Property editors are a complex topic, so we’re going to add a simple type called a tagged property editor. Look at the properties FadeDirection and TextDirection in Figure 3 above. They’re both integers, but if you look at the code (above), you’ll see that those integers have meaning (left, right, and so on) that don’t appear in the property sheet. The developer simply has to know which integer to type in. If an invalid integer is entered, the bean rejects the value but offers no assistance. (Try this: Download the JAR file for all of these examples from the Resources below.)

We can do better. The PropertyEditor interface allows us to take control of how individual properties, or any property of a particular type, are edited in a bean’s container. We’re going to tell the Introspector how to edit the two direction properties by creating a BeanInfo object that describes all of the bean’s properties. The java.beans.BeanInfo interface defines a method called getPropertyDescriptors(), which returns an array of PropertyDescriptor objects. Each PropertyDescriptor object describes one property of the bean to which the BeanInfo corresponds.

We derive the ColorFadeBeanBeanInfo class from java.beans.SimpleBeanInfo, a convenience class that implements the entire BeanInfo with do-nothing methods. That way, a programmer can override only the methods needed for a desired result and not have to implement the entire BeanInfo interface.

The ColorFadeBeanBeanInfo class source code looks like this:

001 import java.beans.*;
002 import ColorFadeBean;
003 import FadeDirectionEditor;
004 import java.beans.*;
005 
006 public class ColorFadeBeanBeanInfo extends SimpleBeanInfo {
007     // PROPERTY DESCRIPTORS
008     // Makes it easier to create property descriptors
009     protected PropertyDescriptor prop(String sName_, String sDesc_,
010                                       boolean isBound_)
011         throws IntrospectionException {
012         PropertyDescriptor pd = new PropertyDescriptor(sName_,
013                                                        ColorFadeBean.class);
014         pd.setShortDescription(sDesc_);
015         pd.setBound(isBound_);
016         return pd;
017     }
018     // Creates a property descriptor AND sets its editor class
019     protected PropertyDescriptor prop(String sName_, String sDesc_,
020                                       boolean isBound_, Class classEditor_)
021         throws IntrospectionException {
022         System.out.println("Set editor for property " + sName_);
023         PropertyDescriptor pd = prop(sName_, sDesc_, isBound_);
024         pd.setPropertyEditorClass(classEditor_);
025         return pd;
026     }
027     // Introspection finds this and uses it to configure property sheet
028     public PropertyDescriptor[] getPropertyDescriptors() {
029         try {
030             PropertyDescriptor pds[] = {
031                 prop("Height",        "Height", false),
032                 prop("Width",         "Width", false),
033                 prop("Message",       "The message to display", false),
034                 prop("MessageFont",   "The font of the message",  false),
035                 prop("TextDirection", "The text alignment",
036                                       true, TextDirectionEditor.class),
037                 prop("MessageColor",  "The color of the text", false),
038                 prop("ColorFrom",     "The color at which fade begins", false),
039                 prop("ColorTo",       "The color at which fade ends", false),
040                 prop("FadeDirection", "The direction in which color fades",
041                                       true, FadeDirectionEditor.class)
042             };
043             return pds;
044         }
045         // This should never occur...
046         catch (IntrospectionException e) {
047             System.out.println("getPropertyDescriptors() failed for " +
048                                "ColorFadeBean: " + e.getMessage());
049             return null;
050         }
051     }
052 };

Two methods, both called prop(), serve as convenience functions for creating PropertyDescriptor objects (lines 9-26). One of the two prop() methods takes a Class object, which is the editor for that particular property. (Ignore the isBound_ argument for the moment. We’ll discuss this in the section on property change listeners below.) The method getPropertyDescriptors() returns an array of PropertyDescriptor objects (lines 28-51). When the BeanBox asks the Introspector class for information about the ColorFadeBean, the Introspector finds the ColorFadeBeanBeanInfo class and calls its getPropertyDescriptor() method. The array that is returned is used to populate the property sheet.

Notice that some of the properties that were previously on the property sheet (foreground, background, and name) are gone. Why is this? The Introspector class asks a bean class for its property descriptors by retrieving the BeanInfo associated with the bean, and then calling getPropertyDescriptors(). If that method returns null, or if there is no BeanInfo at all, the Introspector creates a BeanInfo. It does this by using low-level reflection (a JDK 1.1 feature) to analyze the class file, identifying properties by their signatures (in accordance with the naming conventions) and creating a list of PropertyDescriptors. So, since we’ve provided our own list of properties, that reflection never happens, and therefore those other properties (which are inherited from Component) are never detected.

A property editor class is a subclass of java.beans.PropertyEditor which can be used to edit a bean property. It is usually implemented as a subclass of java.beans.PropertyEditorSupport (another one of those do-nothing convenience classes). The FadeDirectionEditor, for example, looks like this:

001 import ColorFadeBar;
002 import java.beans.*;
003 
004 // Edit fade direction
005 public class FadeDirectionEditor extends java.beans.PropertyEditorSupport {
006     public String[] getTags() {
007         return new String[] { "Left", "Right", "Up", "Down" };
008     }
009     public void setAsText(String sValue)
010         throws IllegalArgumentException {
011         if (sValue == "Left")
012             setValue(new Integer(ColorFadeBar.LEFT));
013         else if (sValue == "Right")
014             setValue(new Integer(ColorFadeBar.RIGHT));
015         else if (sValue == "Up")
016             setValue(new Integer(ColorFadeBar.UP));
017         else if (sValue == "Down")
018             setValue(new Integer(ColorFadeBar.DOWN));
019         else
020         {
021             throw new IllegalArgumentException(sValue);
022         }
023     }
024     public String getAsText() {
025         int iValue = ((Integer)getValue()).intValue();
026         String s = "Illegal value";
027         switch (iValue) {
028             case ColorFadeBar.LEFT:     s = "Left";    break;
029             case ColorFadeBar.RIGHT:    s = "Right";   break;
030             case ColorFadeBar.UP:       s = "Up";      break;
031             case ColorFadeBar.DOWN:     s = "Down";    break;
032             case ColorFadeBar.CENTER:   s = "Center";  break;
033         }
034         return s;
035     }
036 };

Lines 6-8 represent the getTags() function, which returns a list of the labels to use for the drop-down list in the property sheet. setAsText() (lines 9-23) and getAsText() (lines 24-35) allow the container (the BeanBox) to get and set the property value in terms of the tags. The TextDirectionEditor works exactly the same way.

Including the ColorFadeBeanBeanInfo, TextDirectionEditor, and FadeDirectionEditor classes in the JAR file and running the BeanBox produces the following results:

Figure 4: Bean with property sheet, including custom property editors

There are more complex property editors that you may want to explore. See the JavaBeans books reviewed in last month’s column.

Now that we can control how the bean looks, let’s add some control to what it does.

Task 4: Reach out and touch some bean

Previous columns on event listeners (see Resources) have described how to make your beans listen for events from other components. Our new bean become an event receiver, listening for changes to properties of other objects by implementing the interface java.beans.PropertyChangeListener. This interface contains a single method, propertyChange(), which is your bean’s “receiver” for all of the property changes it’s listening for. It’s this method’s responsibility to figure out what has changed and what to do about it.

Likewise, your bean can become an event source itself by implementing the methods addPropertyChangeListener() and removePropertyChangeListener(), which allow other objects to request notification when any of your bean’s properties change. The most common way to implement these functions is to defer calls to them to an instance of the convenience class PropertyChangeSupport, which manipulates a list of event listeners in a thread-safe manner. PropertyChangeSupport has an additional method, firePropertyChange, which the bean uses to notify all registered listeners of changes in its properties.

We’re going to extend our ColorFadeBean by implementing PropertyChangeListener, and also set up notification for some of our properties.

We add the following code to the ColorFadeBean:

010     implements Serializable, PropertyChangeListener   // Every JavaBean must be Serializable
...
012     protected PropertyChangeSupport pcs_ = new PropertyChangeSupport(this);
...
058     // Fade direction
059     public void setFadeDirection(int iNewFadeDirection)
060     {
061         // If new direction is out of range, ignore
062         if (iNewFadeDirection >= 0 || iNewFadeDirection <= 4) {063             pcs_.firePropertyChange("FadeDirection",
064                                     new Integer(_iFadeDirection),
065                                     new Integer(iNewFadeDirection));
066 
067             _iFadeDirection = iNewFadeDirection;
068             repaint();
069         }
070     }
...
074     public void setTextDirection(int iNewTextDirection)
075     {
076         // If new direction is out of range, throw
077         // exception
078         if (iNewTextDirection == LEFT || iNewTextDirection == RIGHT ||
079             iNewTextDirection == CENTER) {
080 081             pcs_.firePropertyChange("TextDirection",
082                                     new Integer(_iTextDirection),
083                                     new Integer(iNewTextDirection));
084 
085             _iTextDirection = iNewTextDirection;
086             repaint();
087         }
088     }
...
100     //
101     // Property change listeners
102     //
103     public void addPropertyChangeListener(PropertyChangeListener l)
104     {
105         System.out.println("Added property change listener");106         pcs_.addPropertyChangeListener(l);
107     }
108 
109     public void removePropertyChangeListener(PropertyChangeListener l)
110     {111         pcs_.removePropertyChangeListener(l);
112     }
113 
114     //
115     // Listen for property changes
116     //
117 
118     //
119     // A property change occurred in something I'm listening to
120     //121     public void propertyChange(PropertyChangeEvent evt)
122     {
123         String sPropertyName = evt.getPropertyName();
124         Object  objNewValue   = evt.getNewValue();
125         Object  objOldValue   = evt.getOldValue();
126 
127         System.out.println("Got a property change event for " + sPropertyName);
128 
129         if (sPropertyName == "FadeDirection") {
130             setFadeDirection(((Integer)objNewValue).intValue());
131         } else if (sPropertyName == "TextDirection") {
132             setTextDirection(((Integer)objNewValue).intValue());
133         }
134     }
135 }

You see above that firePropertyChange() is called each time a particular property changes (lines 63-65 and 81-83). This notifies all listeners that might have registered with addPropertyChangeListener() (line 106) that a property change has occurred. Such listeners receive a PropertyChangeEvent, which encapsulates the source object and the old and new values of the property.

The function propertyChange() (lines 121-134) implements interface java.beans.PropertyChangeListener (line 10), making the bean a property change event receiver. The propertyChange() method checks two properties: FadeDirection and TextDirection, and updates its own properties accordingly. Essentially, this bean can call some other object’s addPropertyChangeListener() method and be notified any time that object’s properties change. If the other object has a property called TextDirection, this bean will set its own text direction property accordingly. This pattern of one object tracking another object’s property is called a bound property. Bound properties are supported directly by the BeanBox, which figures out which events can be hooked to which listeners, and allows the developer to wire beans up visually.

Figure 5: Event listeners in action

In the example in Figure 5 above, two ColorFadeBeans are in the same BeanBox. Using the Edit->Bind Property... menu item in the BeanBox, the bean labelled Event Receiver has its FadeDirection and TextDirection properties bound to those of the other bean. As you can see from the animation in Figure 4, changing the property in one bean changes the same property in another. We’ve created behavior between two components with no programming!

Conclusion

In this installment of the JavaBeans column, we’ve used many of the concepts covered in this column throughout the past year. You may want to continue to experiment with these classes, writing more property editors, adding properties to the bean, and so on. Please send me e-mail and show me what you’ve done.

You’ll find the source code and JAR file for the applet and bean in the Resources below. You can do anything you like with them except claim you wrote them.

I’m going to be taking a vacation from this column. I’d like to take this opportunity to thank my readers for their support and interest. I’d especially like to thank those who have taken the time to write.

I’d like to leave you with an inspiring quotation that I keep on my wall. Whenever I’m not sure where to go next with JavaBeans, reflection (ahem) on this quotation always gets me back on track. The Beanitudes (be-AN-i-tudes)

Blessed are they who use accessors,

For their internal state shall be private.

Blessed are they who follow naming conventions,

For their properties shall be automatically exposed.

Blessed are the subclasses,

For they shall inherit both the public and the protected.

Blessed are the event listeners,

For they shall be notified.

Blessed are the Serializable,

For they shall be persistent.

Blessed are the providers of BeanInfo,

For they shall be introspective,

Blessed are the Customizers,

For they shall be configured.

Mark Johnson has a B.S. in computer and electrical engineering from Purdue University (1986). He is a fanatical devotee of the design pattern approach in object-oriented architecture, of software components in theory, and of JavaBeans in practice. Over the past several years, he’s worked for Kodak, Booz-Allen and Hamilton, and EDS in Mexico City, developing Oracle and Informix database applications for the Mexican Federal Electoral Institute and for Mexican Customs. He currently works as a designer and developer for Object Products Inc. in Fort Collins, CO.