HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

One of the great things about the JavaBeans specification is the flexibility it affords component developers in how they package their beans. As a bean developer, all you need is a class with a no-argument constructor that supports serialization and it's a bean. If you follow some simple naming conventions, most integrated development environments (IDEs) can tell enough about your bean to be able to use it for visual application development. While this is sufficient for some simple beans, the beans specification also provides ways for bean developers to explicitly give information that the IDE can use to assist the application developer in using the component. This article explores some of the ways that bean developers can make their components more usable by application developers through the use of custom property editors and customizers.

JavaBeans can be used by software development tools to allow visual programming using the beans as reusable components. This can reduce the amount of code that application developers need to write to make an application out of beans components. Beans are characterized by their properties and the events they generate and receive. Most beans have some properties that the application developer can configure during application construction to set the initial state of the bean. The Swing components are examples of beans with many properties that can be configured during application construction. They have properties like "backgroundColor" and "font" that control how the component looks and behaves. The bean developer needs only to follow some simple naming conventions when naming accessor methods to take advantage of this flexibility. If the bean has a property named "backgroundColor", a development tool will look for a method called "getBackgroundColor" that can be used to read the state of the property from the bean. If the bean has a method called "setBackgroundColor", it will be used to change the state of the property. Each of these methods is optional. That is, a bean can have a read-only property or a write-only property. Most beans are visual user interface components (like the Swing components), but beans can also be classes that are not visible on the user interface.

Development environments use Java introspection to interrogate a bean for its properties by looking for methods called get<something> and set<something> unless the property is a boolean; in that case the read accessor is called is<something>. With this information the IDE can build a graphical user interface called a customizer that the application developer can use to manipulate the bean. Often this is good enough. Simple beans may have properties that are of simple types and whose names are self-explanatory. Most IDEs include GUI components for Java primitive types and common classes like java.awt.Color and java.awt.Font, but if your bean has a property of a type that you defined, or isn't included in the IDE's set of property editors, it will just omit the property from the bean's automatically generated customizer. Also, more complicated beans may have dozens of properties - most of which are not important to the bean user - or have complicated dependencies that aren't visible through introspection. In these cases it would make the bean more useful if you could provide a simpler customizer that guided the user in how to configure the bean and helped prevent errors.

While the JavaBeans specification allows for IDEs to create customizers for beans using introspection, it also allows for bean developers to provide customizers to be used in place of, or in addition to, the default customizer. Bean developers have the flexibility to simply provide an editor for a single property to be used within the default customizer, or to provide a complete GUI customizer that can replace the default customizer altogether.

Property Editors
There are several ways to provide a custom property editor to be used within the default customizer that the IDE generates by introspection. I'll explore the simplest here with an example and then move on to a full-blown customizer.

Let's make a simple bean that's a digital clock. Its only important property controls whether it displays the time as 12-hour time with an AM/PM designation or as 24-hour time. I'll call this boolean property "twentyFourHourFormat" and when the property is true, the clock will use 24-hour time; when it's false, it will use 12-hour AM/PM time. The code for this bean is in Listing 1.

Beans-aware IDEs have a property editor for the boolean primitive type; Figure 1 shows the customizer and bean display that the Sun BeanBox provides for the Clock bean.
Figure 1
Figure 1-1
Figure 1:

The BeanBox is Sun's reference implementation of a bean container. It's a part of the Beans Development Kit (BDK) and is available at http://java.sun.com/beans. The BeanBox is not a full-blown IDE, but it's a good tool for testing beans, property editors and customizers. In this case the BeanBox used introspection to generate the customizer panel that includes not only the twentyFourHourFormat property but also all of the properties from the bean's base classes. Notice that the property editor for the twentyFourHourFormat property is a combo box of the values true and false. The Introspector also found a property called "running" because the bean has methods called isRunning and setRunning. The "running" property should be hidden from the application developer so that the clock runs continuously. I'll show how to hide this property from the application developer later in this article.

The automatically generated customizer isn't bad, but we can make it a little more user-friendly. Let's define a custom property editor that gives the user more information about what the property does.

All property editors must implement the java.beans.PropertyEditor interface. The easiest way to create a simple PropertyEditor is to extend the java.beans.PropertyEditorSupport class that implements java.beans.PropertyEditor and defines all of the interface's methods to reasonable defaults. Then I'll just need to override the methods that are necessary for the features I want to provide.

Instead of "true" and "false," it would be nice if the application developer could choose between "12 hour" and "24 hour" as the time format. I can do that with a custom property editor. If I override the getAsText() and setAsText() methods from PropertyEditorSupport, I can tell the IDE that the property can be set and read as a text string. If I also override the getTags() method, I can tell the IDE what the valid string values are for this property. The code for the property editor is shown in Listing 2.

Now the IDE can build a customizer that uses the strings that are in the property editor rather than true and false. But how does the IDE associate the property editor with the property? I'll need a BeanInfo class to make that association. A BeanInfo class provides information about a bean that isn't available through introspection. It can also be used to override the results of introspection. BeanInfo classes must implement the java.beans.BeanInfo interface. As in the case of the PropertyEditor interface, there is a class called java.beans.SimpleBeanInfo that implements all of the BeanInfo methods with reasonable defaults. I'll extend SimpleBeanInfo to define the ClockBeanInfo.

When examining a bean, an IDE looks for the BeanInfo class by appending "BeanInfo" to the name of the bean class and looking for a class by that name. If my bean is named mybeans.Clock, an IDE will look for the class mybeans.ClockBeanInfo that implements the BeanInfo interface. Ordinarily, a BeanInfo class goes in the same package with the bean it describes, but it can go in another package. IDEs will also look for BeanInfo classes in the packages returned by the static method in java.beans.Introspector called getBeanInfoSearchPath().

You can add your BeanInfo package to the search path with setBeanInfoSearchPath(). Thus, if your BeanInfo classes were all in the package mybeans.beaninfos, you could call this to tell an IDE about it:

String [] path = {"mybeans.beaninfos"}; Introspector.setBeanInfoSearchPath(path);

If the IDE finds a BeanInfo for a bean, either in the same package as the bean or in the BeanInfo search path, it will ask the BeanInfo for information about the bean before using introspection.

An IDE uses the getPropertyDescriptors() method to get information about the bean's properties from the BeanInfo class. getPropertyDescriptors() returns an array of java.beans.PropertyDescriptor objects. PropertyDescriptors define how the property should be displayed and edited. PropertyDescriptor and its superclass FeatureDescriptor also follow the beans naming convention for get and set accessors to properties. Table 1 summarizes the properties of a PropertyDescriptor.

Table 1

The BeanInfo getAdditionalBeanInfo() method is used by the IDE to get properties from the ancestor classes of the bean. It relieves the BeanInfo class of the responsibility for creating PropertyDescriptors for all of the properties of all of the superclasses of the bean. I'll include getAdditionalBeanInfo() here so the inherited Swing properties are also displayed in the automatically generated customizer. The code for the ClockBeanInfo class is in Listing 3.
The BeanInfo class can also be used to associate a set of icons with the bean. IDEs can use the icons in their palette as a graphical representation of the bean. The getIcon() BeanInfo method takes a request from the IDE for a type (color or monochrome) and size (16 or 32 pixels square) of icon and returns a java.awt.Image object if an icon of the requested type is available. Constants representing the different icon types are defined in the BeanInfo interface. As we'll see, different IDEs use different icon types so it's a good idea to include a variety if possible. The getIcon() method is also listed in Listing 3.

Once the BeanInfo and PropertyEditor classes are written, I can load them into the BeanBox and see the results. Notice how the property editor for the twentyFourHourFormat property now uses the information from the PropertyDescriptor in the BeanInfo to make the editor user interface. The name of the property is now listed as "Time Format" rather than "twentyFourHourTime" and the combo box contains "12 Hour" and "24 Hour" rather than true and false. Because the "running" property descriptor hidden property was set to true in ClockBeanInfo.getPropertyDescriptors(), the property doesn't appear in the customizer that the BeanBox generated (see Figure 2)

Figure 2
Figure 2:

Customizers
By adding the BeanInfo and PropertyEditor, we've hopefully made the Clock bean a little easier to use in an application. I can take this one step further by asserting more control over the bean configuration process and define a customizer that can replace the default customizer generated by the IDE. By doing this I can show the user only the properties that are important and present the bean state in any way I want.

Like custom property editors, customizers are also associated with their beans through the BeanInfo class. The BeanInfo method getBeanDescriptor() returns a BeanDescriptor that contains the bean's class and the bean's customizer class if it exists. An IDE can use the bean descriptor to find out whether the bean's author has provided a customizer that the IDE can use instead of or in addition to the customizer it generates using BeanInfo and introspection. SimpleBeanInfo.getBeanDescriptor() asserts that there is no customizer, so to assert that there is one, I'll override getBeanDescriptor() in ClockBeanInfo in Listing 3.

Now I'll need to write the customizer. All beans customizers must implement the java.beans.Customizer interface and extend java.awt.Panel. The code for the simple Clock customizer is in Listing 4.

The IDE gives the customizer a reference to the to-be-configured object by calling the set-Object method of the Customizer interface. The customizer can then synchronize its state with the object being customized, register as an event listener, etc.

Then, as the application developer manipulates the customizer GUI, the bean can be updated immediately. This gives the application developer immediate feedback on what the effects are of changing a property.

To view the customizer in the BeanBox, first select the bean to be customized, then select View->Customizer from the menu bar. Figure 3 shows what the customizer looks like in the BeanBox.

Figure 3
Figure 3:

Packaging the Bean
Once the bean, BeanInfo, PropertyEditors and customizer have been written, they need to be packaged for loading into an IDE. The easiest way to load the bean and its associated classes and images into an IDE is to put them into a JAR file using a command like this.

jar cvmf manifest.mf mybeans.jar mybeans\*.class mybeans\*.gif

The manifest file is an annotated list of the files that go into the JAR file. A special tag in the manifest file called "Is JavaBean" identifies the classes that are JavaBeans. The manifest file for this example is:

Manifest-Version: 1.0
Name: mybeans/Clock.class
Java-Bean: True

Every IDE loads beans a little differently, but I can go through a couple of examples here. All of the examples so far have used the BeanBox that comes with the Sun Beans Development Kit. Here's one way to load the JAR file into the BeanBox:

  1. Under the File menu, select LoadJar.
  2. Use the file dialog to select the JAR file that contains the beans. The BeanBox loads all of the beans in the Jar file into the BeanBox palette.
Figure 4 shows what the Clock bean looks like in the BeanBox palette. Note that it found the 16x16 color icon and put it in the palette with the bean class name.

Figure 4
Figure 4:


A Real IDE
Borland's JBuilder uses a little different method but can load the same JAR file. These are the steps to load the Clock bean into the JBuilder 3 palette:

  1. Select Tools -> Configure Palette.
  2. Under the "Pages" tab, select the palette page to hold the component. A blank page called "Other" included in the default palette is a good place for new beans.
  3. Select the "Add From Archive" tab and select the JAR file containing the bean and related classes. JBuilder opens the JAR file, reads the manifest and displays the beans that it found.
  4. Select the bean class or classes to be installed.
  5. Select "Install" to load the component.
Figure 5 shows what the component looks like in JBuilder. It looks like JBuilder preferred the 32x32 color icon to represent the clock bean in its palette.

Figure 5
Figure 5:

Let's take a closer look at how JBuilder uses the Clock bean and its associated classes. If I drop the Clock from the palette onto a panel, JBuilder shows the clock in the panel and the Clock is running. JBuilder also creates a property sheet customizer containing the properties that I defined in the BeanInfo getPropertyDescriptors() method and the Swing properties that it found by using getAdditionalBeanInfo(). The Clock bean property sheet is shown in Figure 6.

Figure 6
Figure 6:

IDEs have a lot of flexibility in how they use the BeanInfo information. Notice that JBuilder did use the property editor for the twentyFourHourTime property, but used the property name rather than the display name I set in the BeanInfo. It did pick up the short description text from the BeanInfo and used it as the ToolTip help text (see Figure 7). Cool. JBuilder also added two entries to the property sheet that are not bean properties. The "name" is the name that JBuilder will give the Clock variable in the code that it generates. "Constraints" refers to the layout constraints used when the Clock component is added to its container.

Figure 7
Figure 7:

The custom ClockCustomizer looks a lot like it did in the BeanBox. It's displayed by right-clicking on the Clock component and selecting Customizer from the popup menu. JBuilder's version of the ClockCustomizer is shown in Figure 8.

Figure 8
Figure 8:

JBuilder also has a design view that shows the user interface component tree. In this view JBuilder shows the Clock bean contained within a JPanel (see Figure 9). Here it used the 16x16 color icon that I specified in ClockBeanInfo.

Figure 9
Figure 9:

Debugging a Customizer
This example is a simple customizer, but for a complicated customizer it might be necessary to use a debugger to get it working just right. Once the bean and customizer are loaded into an IDE, it can be pretty difficult to find and fix problems. Sometimes even the time-tested method of adding System.out messages won't work because there is no console to print to. I've found that it's worth taking the time to write code that can be used to test customizers outside of an IDE. That way I can use System.out or a debugger to see what the customizer is doing. This simple code loads a bean and its customizer into two windows so they can be tested and debugged.

Clock c = new Clock();
JFrame f = new JFrame("Bean");
f.getContentPane().add(c);
f.pack();
f.show();

ClockCustomizer cust = new ClockCustomizer();
f = new JFrame("Customizer");
f.getContentPane().add(cust);
f.pack();
f.show();
cust.setObject(c);

Figure 10 shows what the test harness looks like.
This simple test harness can make debugging a customizer much easier. Insert this code into the main() method of the customizer for a built-in test capability. With a little additional code this test driver could also be used to test bean serialization and event handling.

Figure 10
Figure 10:

Using custom property editors and customizers are great ways to make your beans more usable by application developers regardless of what development tools they use. Take the time to include these features in your beans and your users will thank you for it.

Author Bio
William Wright is a senior software engineer with GTE Corporation in Arlington, Virginia. He has 10 years' experience with real-time systems development and object-oriented programming. He can be reached at: [email protected]

	

Listing 1:  The Clock Bean 

package mybeans; 
  

import java.awt.*; 
import java.util.*; 
import java.text.*; 
import javax.swing.*; 
import java.io.*; 
  

public class Clock extends JLabel 
     implements Runnable, Serializable{ 
  DateFormat formatter12; 
  DateFormat formatter24; 
  private boolean running; 
  private boolean isTwentyFourHourTime; 
  

  public Clock() { 
    formatter12 = 
     new SimpleDateFormat("h:mm:ss a"); 
    formatter24 = 
         new SimpleDateFormat("H:mm:ss"); 
    setRunning(true); 
  } 
  

  public void run() { 
  while(running) 
  { 
   if (isTwentyFourHourTime()) 
    this.setText( 
     formatter24.format(new Date())); 
    else 
     this.setText( 
      formatter12.format(new Date())); 
     try { 
       Thread.sleep(1000); 
     } 
     catch (InterruptedException e) {} 
   } 
} 
  

// The running property should be hidden 
public void setRunning(boolean newRunning) { 
  boolean  oldRunning = running; 
  running = newRunning; 
  Thread t = new Thread(this); 
  t.start(); 
  firePropertyChange("running", 
              new Boolean(oldRunning), 
              new Boolean(newRunning)); 
} 
  

public boolean isRunning() { 
  return running; 
} 
  

// The twentyFourHourTime property 
// should be exposed 
public void setTwentyFourHourTime( 
       boolean newTwentyFourHourTime) { 
boolean  oldTwentyFourHourTime = 
       isTwentyFourHourTime; 
isTwentyFourHourTime = newTwenty- 
FourHourTime; 
firePropertyChange("twentyFourHourTime", 
    new Boolean(oldTwentyFourHourTime), 
    new Boolean(newTwentyFourHourTime)); 
} 
  

public boolean isTwentyFourHourTime() { 
  return isTwentyFourHourTime; 
} 
} 
  

Listing 2:  The Property Editor 

package mybeans; 
  

import java.beans.*; 
  

public class ClockPropertyEditor 
    extends PropertyEditorSupport { 
  

  private boolean is_24 = false; 
  

  public ClockPropertyEditor() { 
  } 
  

  public String getAsText() { 
    return (is_24 ? "24 hour" : "12 
    hour"); 
  } 
  

  public Object getValue() { 
    return new Boolean(is_24); 
  } 
  

  public void setAsText(String value) 
         throws IllegalArgumentException { 
    if (value.equals("24 hour")) 
      setValue(new Boolean(true)); 
    else if (value.equals("12 hour")) 
      setValue(new Boolean(false)); 
    else 
      throw new IllegalArgumentException( 
                "Unrecognized value: " + value); 
  } 
  

  public void setValue(Object value) { 
    Boolean b = (Boolean)value; 
    is_24 = b.booleanValue(); 
    firePropertyChange(); 
  } 
  

  public String[] getTags() { 
    String [] tags = {"24 hour", "12 hour"}; 
    return tags; 
  } 
} 
  

Listing 3:  The ClockBeanInfo Class 

package mybeans; 
  

import java.beans.*; 
  

public class ClockBeanInfo 
       extends SimpleBeanInfo { 
  public ClockBeanInfo() { 
  } 
  

  public 
  PropertyDescriptor[] getPropertyDescriptors() { 
    try  { 
      // PropertyDescriptor for the 
      // "twentyFourHourTime" property 
      PropertyDescriptor pd1 = 
        new PropertyDescriptor( 
            "twentyFourHourTime", 
             Clock.class, 
            "isTwentyFourHourTime", 
            "setTwentyFourHourTime"); 
      pd1.setDisplayName("Time Format"); 
      pd1.setShortDescription( 
        "Controls whether the time is "+ 
        "displayed in 12 or 24 hour mode"); 
      pd1.setBound(true); 
      pd1.setPropertyEditorClass( 
        ClockPropertyEditor.class); 
  

      // PropertyDescriptor to hide the 
      // "running" property 
      PropertyDescriptor pd2 = 
        new PropertyDescriptor( 
            "running", 
            Clock.class, 
            "isRunning", 
            "setRunning"); 
      pd2.setHidden(true); 
  

      PropertyDescriptor[] pds = new PropertyDescriptor[] {pd1, pd2}; 
      return pds; 
    } 
    catch(IntrospectionException ex) { 
      return null; 
    } 
  } 
  

  public BeanDescriptor getBeanDescriptor() { 
    return new BeanDescriptor( 
      Clock.class, 
      ClockCustomizer.class); 
  } 
  

  public java.awt.Image getIcon(int iconKind) { 
    switch (iconKind) { 
      case BeanInfo.ICON_COLOR_16x16: 
        return loadImage("Clock16x16Color.gif"); 
      case BeanInfo.ICON_COLOR_32x32: 
        return loadImage("Clock32x32Color.gif"); 
      case BeanInfo.ICON_MONO_16x16: 
        return loadImage("Clock16x16Mono.gif"); 
      case BeanInfo.ICON_MONO_32x32: 
        return loadImage("Clock32x32Mono.gif"); 
    } 
    return null; 
  } 
  

  public BeanInfo[] getAdditionalBeanInfo() { 
    Class superclass = Clock.class.getSuperclass(); 
    try  { 
      BeanInfo superBeanInfo = Introspector.getBeanInfo(superclass); 
      return new BeanInfo[] { superBeanInfo }; 
    } 
    catch(IntrospectionException ex) { 
      return null; 
    } 
  } 
} 
  

Listing 4:  The Clock Customizer 

package mybeans; 
  

import java.awt.*; 
import java.beans.*; 
import java.awt.event.*; 
import javax.swing.*; 
import javax.swing.event.*; 
  

public class ClockCustomizer extends JPanel 
                      implements Customizer{ 
  protected Clock theClockBean = null; 
  private JRadioButton button24 = 
      new JRadioButton("24 Hour Mode"); 
  private JRadioButton button12 = 
      new JRadioButton("12 Hour Mode"); 
  

  public ClockCustomizer() { 
    JLabel topLabel = new JLabel(); 
    topLabel.setFont( 
      new java.awt.Font("Dialog", 1, 20)); 
    topLabel.setHorizontalAlignment( 
      SwingConstants.CENTER); 
    topLabel.setText( 
      "This controls the mode of the clock"); 
    this.setLayout(new BorderLayout()); 
    button24.addChangeListener( 
      new ChangeListener() { 
        public void stateChanged(ChangeEvent e) { 
          button24Changed(e);}}); 
    this.add(topLabel, BorderLayout.NORTH); 
    JPanel radioPanel = new JPanel(); 
    this.add(radioPanel, BorderLayout.CENTER); 
    radioPanel.add(button24, null); 
    radioPanel.add(button12, null); 
    ButtonGroup grp = new ButtonGroup(); 
    grp.add(button12); 
    grp.add(button24); 
    button12.setSelected(true); 
  } 
  

  public void setObject(Object bean) { 
    theClockBean = (Clock)bean; 
    button24.setSelected( 
      theClockBean.isTwentyFourHourTime()); 
  } 
  

  void button24Changed(ChangeEvent e) { 
    theClockBean.setTwentyFourHourTime( 
      button24.isSelected()); 
  } 
} 
  

  
  
      
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.