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 appletWe’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 propertiesThe 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 ColorFadeBarThe 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 appletHEIGHTThe height of the appletSTARTCOLORThe beginning color for the background fadeENDCOLORThe ending color for the background fadeTEXTCOLORThe color in which to render the messageTEXTThe message textFADEDIRBackground fade direction (left, right, up, down)TEXTDIRText alignment (left, center, right)FONTThe name of the font for TEXTFONTSIZEThe size of the font for TEXTX0Text left or right margin offset from applet edge(depending on TEXTDIR; default 5)DYText baseline offset (default 0) Table 1. Parameters to existing appletThese 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 appletHEIGHTWidthintWidth of the appletWIDTHMessageStringThe message to displayTEXTMessageFontFontThe font of the messageFONT, FONTSIZETextDirectionintThe text alignmentTEXTDIRMessageColorColorThe color of the textTEXTCOLORColorFromColorThe color at which fade beginsSTARTCOLORColorToColorThe color at which fade endsENDCOLORFadeDirectionintThe direction in which color fadesFADEDIR Table 2. Properties of the new JavaBeanNotice 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 propertiesNow 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 editorsOur 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 editorsThere 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 beanPrevious 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 actionIn 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!ConclusionIn 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. Java