Specifying component information with BeanInfo classes

When you create a JavaBeans component and install it on the Component Palette, usually you want the properties and events to appear in JBuilder's Component Inspector. If you followed the JavaBeans design and naming conventions while creating your bean, all the properties and events you defined plus all those inherited from superclasses appear automatically.

The JavaBeans standards were designed, in part, to facilitate the use of tools such as JBuilder. Regular naming and carefully designed method signatures allow JBuilder to examine component code and identify component features. This capability of examining a bean and extracting information about it automatically is called introspection.

What if you don't want to use the JavaBeans design and naming conventions or you have existing classes that don't use them? Or what if you don't want to give the user of your bean access to every property at design time? To handle these situations, create a class that implements the BeanInfo interface.

By creating a class that implements BeanInfo, you provide explicit information about your JavaBeans component. Creating a BeanInfo class for your component is optional; it is necessary only when you want to provide explicit information about a bean to JBuilder, and not have JBuilder derive the information it needs through automatic introspection.

This chapter covers these topics about using the BeanInfo interface:


Turning existing classes into JavaBeans

It's possible many of your existing Java classes could be useful JavaBeans. They may have been developed before the JavaBeans specification was finalized and they do not comply with most or all of the design and naming standards required by the specification.

If your component does not conform to the design and naming standards, you can create a BeanInfo class to deliver information about it to JBuilder and other tools. With every feature, JBuilder looks first for an information class that implements BeanInfo. If it doesn't find it, JBuilder uses direct examination of the component's code to find conforming properties, methods, and events. Because of this strict order of examination, you can use the BeanInfo classes in two ways:

Specifying complete component information

If your class follows none of the JavaBeans naming and design standards, use a BeanInfo class to specify every component feature. You can create your own class that implements BeanInfo, or you can extend the BasicBeanInfo class. The following steps outline how to implement BeanInfo directly:

  1. Write a class that implements java.beans.BeanInfo. Name it by appending "BeanInfo" to your class name and place the class in the same package as the component it accompanies. For example, if MyComponent needs a component information class, you would write the following:
  2. public class MyComponentBeanInfo implements java.beans.BeanInfo
    
  3. Implement the getBeanDescriptor() method, including code to instantiate the BeanDescriptor object to hold general information about the component. For example:
  4. static final class  beanClass=borland.samples.comptest.MyComponent.class;
    
    public BeanDescriptor getBeanDescriptor() {
      try{
        return new BeanDescriptor(beanClass); //no customizer for this bean
      }
      catch(IntrospectionException ie){
        return null; 
      }
    }
    
    In the code above, the BeanDescriptor object's constructor takes an argument that sets its beanClass property to the name of the component being explained (MyComponent). Optionally, it could also take an argument to set the customizerClass property to the name of the component's special editor (if any).

  5. Implement the getEventSetDescriptors() method, including code to instantiate an array of EventSetDescriptor objects containing information on each event set that your component generates. For example, MyComponent generates content events grouped on a custom listener interface. Here is the code that explains the event set:
  6. static final String[] eventSetNames = {"content"}; 
    static final String[] eventListeners = {"borland.samples.comptest.ContentListener"};
    static final String[] eventListenerMethods = {"contentChanging","contentChanged"};
    
    public EventSetDescriptor[] getEventSetDescriptors() {
      try{
        EventSetDescriptor[] esds = new EventSetDescriptor[eventSetNames.length];
        for (int i=0; i<esds.length; i++)
          esds[i] = new EventSetDescriptor(beanClass, eventSetNames[i], 
                  eventListeners[i], eventListenerMethods[i], "addContentListener", "removeContentListener");
        return esds;
      }
      catch(IntrospectionException ie) {
      }
      return null; 
    }
    
    eventSetNames is an array of all event sets generated by MyComponent. eventListeners is an array of all listener interfaces, and eventListenerMethods is an array of all event registration methods used by event sets that MyComponent generates.

  7. Implement the getPropertyDescriptors() method, including code to instantiate an array of PropertyDescriptor objects containing information on each property your component has. For example, MyComponent has quantity, name, tags, and background properties, with accessor methods that do not conform to the specification. Here is the necessary code:
  8. static final String[] propNames = {"quantity", "name", "tags", "background"};
    static final String[] readers = {"getQ", "getName", "getTagArray", "getBKColor"};
    static final String[] writers = {"setQ", "setName", "setTagArray", "setBKColor"};
    static final String[] propTypes = {"int", "java.lang.String", "java.lang.String[]", "java.awt.Color"};
    
    public PropertyDescriptor[] getPropertyDescriptors() {
      try{
        PropertyDescriptor[] pds = new PropertyDescriptor[proNames.length];
        for (int i=0; i<pds.length; i++)
          pds[i] = new PropertyDescriptor(propNames[i]; beanClass, readers[i]; writers[i]);
        return pds;
      }
      catch(IntrospectionException ie){
      }
      return null; }
    
    eventSetNames holds all the names of all event sets MyComponent generates, eventListeners contains the names of all listener interfaces, and eventListenerMethods contains the names of the event registration methods used by event sets MyComponent generates.

  9. Implement the getMethodDescriptors() method, including code to instantiate an array of MethodDescriptor objects containing information on each method your component makes public. For example, MyComponent has process() and reinitialize() methods to explain to JBuilder. Here is the necessary code:
  10. static final String[] methodNames = {"process", "reinitialize"}:
    static final String[][] methodParams = { {}, {"java.lang.String", "java.lang.String"};
    
    public MethodDescriptor[] getMethodDescriptors() {
      try{
        MethodDescriptor[] mds = new MethodDescriptor[methodNames.length];
        for (int i=0; i<mds.length; i++) {
          Class[] params = new Class[methodParams[i].length];
          for (int j=0; j<params.length; j++)
            params[j] = Class.forName(methodParams[i][j]);
          java.lang.reflect.Method m = beanClass.getMethod(methodNames[i], params);
          mds[i] = new MethodDescriptor(m);
        }
        return mds;
      }
      catch(IntrospectionException ie) {
      }
      catch(ClassNotFoundException cnfe){
      }
      catch(NoSuchMethodException nsme) {
      }
      return null; 
    }
    
    methodNames contains the names of all of MyComponent's methods, and methodParams contains all the parameters of the methods. Note that process() takes no parameters.

Specifying partial component information

If your class is a hybrid, where some component features comply with the JavaBeans specification and others do not, you can use the SimpleBeanInfo class to explain some features and let JBuilder examine the component code directly to get the rest of the information.

SimpleBeanInfo implements the BeanInfo interface, but all of the interface methods return null. As a result, JBuilder looks at the code to find that information directly.

Create an information class by extending SimpleBeanInfo and naming the class by appending "BeanInfo" to your component class name. Then, for any feature that does not comply with the JavaBeans design and naming specifications, override the interface method and provide your own descriptor objects. For example, if MyComponent has properties and events that comply with the specification but its methods are noncompliant, simply override the getMethodDescriptors() method and provide a MethodDescriptor object to explain the bean's methods.

You can also use the BasicBeanInfo class to provide partial information about your bean.


Using the BasicBeanInfo class

If you have the source code to Borland's JavaBeans Component Library, you'll find a BasicBeanInfo class in the util package that simplifies the implementation of the BeanInfo interface. Here's how to use BasicBeanInfo:

  1. Create a new bean information class for your component and include BeanInfo as the last part of its name.
  2. Import borland.jbcl.util.BasicBeanInfo.
  3. Extend the BasicBeanInfo class using the extends statement.
  4. Copy everything between these two comments from BasicBeanInfo into your new class:
  5. // Basic Info
    // BeanInfo implementation

    The code contains several comments.

  6. Delete the features in the code that you don't need for your bean.

    For example, if your bean doesn't use a customizer, remove the customizerClass declaration and the accompanying code comments.

  7. Implement the remaining features you do want.
  8. For example, if you want to describe the properties in your bean, define the propertyDescriptors array. BasicBeanInfo includes source code comments that outlines the syntax you must use.

    The one field you must define is the beanClass field.

Here is the BevelPanelBeanInfo class for the BevelPanel component that extends BasicBeanInfo:

package borland.jbcl.control;

import borland.jbcl.util.BasicBeanInfo;

public class BevelPanelBeanInfo extends BasicBeanInfo implements java.io.Serializable
{
  public BevelPanelBeanInfo() {
    beanClass = BevelPanel.class;
    propertyDescriptors = new String[][] {
      {"background",   Res.getString(Res.BI_background), "getBackground",
	      "setBackground"},
      {"bevelInner",   Res.getString(Res.BI_BevelPanel_bevelInner), "getBevelInner",
	      "setBevelInner", borland.jbcl.editors.BevelTypeEditor.class.getName()}, // int
      {"bevelOuter",   Res.getString(Res.BI_BevelPanel_bevelOuter), "getBevelOuter",
	      "setBevelOuter", borland.jbcl.editors.BevelTypeEditor.class.getName()}, // int
      {"doubleBuffered", Res.getString(Res.BI_doubleBuffered), "isDoubleBuffered",
	      "setDoubleBuffered"},
      {"enabled",      Res.getString(Res.BI_enabled), "isEnabled", "setEnabled"},
      {"font",         Res.getString(Res.BI_font), "getFont", "setFont"},
      {"foreground",   Res.getString(Res.BI_foreground), "getForeground",
	      "setForeground"},
      {"layout",       Res.getString(Res.BI_layout), "getLayout", "setLayout"},
      {"margins",      Res.getString(Res.BI_margins), "getMargins", "setMargins"},
      {"opaque",       Res.getString(Res.BI_opaque), "isOpaque", "setOpaque"},
      {"soft",         Res.getString(Res.BI_BevelPanel_soft), "isSoft", "setSoft"},
      {"toolTipText",  Res.getString(Res.BI_toolTipText), "getToolTipText",
	      "setToolTipText"},
      {"textureName",  Res.getString(Res.BI_texture), "getTextureName",
	      "setTextureName", borland.jbcl.editors.FileNameEditor.class.getName()},
      {"visible",      Res.getString(Res.BI_visible), "isVisible", "setVisible"},
    };
  }
}
BevelPanelBeanInfo defines the beanClass field and then creates a String array of property descriptors that describe each property that is accessible from the Component Inspector. Because no other fields are needed, all the other information has been deleted from the class.


Hiding properties and events

Frequently you'll find that there are some properties you don't want to give the user of your bean access to at design time. It's possible this may be the case for some events also, although that is unusual. Use a class that implements BeanInfo to hide properties and events.

There are two ways to hide a property or event so that it doesn't appear in JBuilder's Component Inspector and isn't available at design time to other visual tools. Which one you choose depends on whether the properties or events follow the JavaBeans design and naming conventions.

If all the properties or events follow the JavaBeans standard, follow these steps to hide a property or event:

  1. Create a BeanInfo class for your component.
  2. For properties, implement the getPropertyDescriptors() method, including code to instantiate an array of PropertyDescriptor objects containing information on each property you want to be available at design time. Omit those properties you want to hide.
  3. For events, implement the getEventDescriptions() method, including code to instantiate an array of EventDescription objects containing information on each event you want to be available at design time. Omit those events you want to hide.

If some of the properties or events don't follow the JavaBeans standard, follow these steps to hide a property or event:

  1. Create a BeanInfo class for your component.
  2. For properties, implement the getPropertyDescriptors() method, including code to instantiate an array of PropertyDescriptor objects containing information on each property.
  3. For events, implement the getEventDescriptions() method, including code to instantiate an array of EventDescription objects containing information on each event.

  4. Include in the BeanInfo class a line of code that calls the setHidden() method on the property or event that you want to hide. For example,
       // Hide the model property
       modelProperty.setHidden(true)

Specifying a customized editor for a property

If you've created a property editor for a property in your component, you must use the component's BeanInfo class to make the property editor available to your component for displaying and editing the property's value. To specify a property editor for a property,

  1. Create a BeanInfo class for your component that extends the BasicBeanInfo class.

  2. Create a property descriptors array that holds property descriptor objects, following the pattern used in BevelPanelBeanInfo shown below:
    propertyDescriptors = new String[][] {
      {"background",   Res.getString(Res.BI_background), "getBackground",
	      "setBackground"},
      {"bevelInner",   Res.getString(Res.BI_BevelPanel_bevelInner), "getBevelInner",
	      "setBevelInner", borland.jbcl.editors.BevelTypeEditor.class.getName()}, // int
      {"bevelOuter",   Res.getString(Res.BI_BevelPanel_bevelOuter), "getBevelOuter",
	      "setBevelOuter", borland.jbcl.editors.BevelTypeEditor.class.getName()}, // int
      {"doubleBuffered", Res.getString(Res.BI_doubleBuffered), "isDoubleBuffered",
	      "setDoubleBuffered"},
      {"enabled",      Res.getString(Res.BI_enabled), "isEnabled", "setEnabled"},
      {"font",         Res.getString(Res.BI_font), "getFont", "setFont"},
      {"foreground",   Res.getString(Res.BI_foreground), "getForeground",
	      "setForeground"},
      {"layout",       Res.getString(Res.BI_layout), "getLayout", "setLayout"},
      {"margins",      Res.getString(Res.BI_margins), "getMargins", "setMargins"},
      {"opaque",       Res.getString(Res.BI_opaque), "isOpaque", "setOpaque"},
      {"soft",         Res.getString(Res.BI_BevelPanel_soft), "isSoft", "setSoft"},
      {"toolTipText",  Res.getString(Res.BI_toolTipText), "getToolTipText",
	      "setToolTipText"},
      {"textureName",  Res.getString(Res.BI_texture), "getTextureName",
	      "setTextureName", borland.jbcl.editors.FileNameEditor.class.getName()},
      {"visible",      Res.getString(Res.BI_visible), "isVisible", "setVisible"},
    };
Note how BevelTypeEditor is the fourth parameter for the bevelInner and bevelOuter property descriptors. Follow this same pattern and specify your property editor class as the fourth parameter for the property descriptor of your property. If your property doesn't have a property editor, you can omit the fourth parameter or specify it as "null".


Specifying a default event

Using a BeanInfo class, you can specify a default event for your JavaBean component. If you don't specify a default event, the editor appears and the actionPerformed() method is highlighted when the user double-clicks the component in the UI Designer. If you use BeanInfo to specify an event as the default, however, the method that is called when the event occurs is highlighted in the editor instead. Specify the default event as the event the user is most likely to want to write an event handler for. Add this line of code to the BeanInfo class for your component:
 protected int defaultEventIndex = <index>;
Specify the position of the default event in the set of events described in the getEventDescriptors() method as the value of <index>. To indicate that no event is to be the default, specify a defaultEventIndex value of -1.

Although you can also specify a default property for your bean, default properties have no meaning in the JBuilder environment.


Generating a BeanInfo class with BeansExpress

The simplest way to create a BeanInfo class for your JavaBean is to use BeansExpress. Create your bean with BeansExpress, specifying the properties, events, and custom editors you need.

To create a BeanInfo class for your bean,

  1. Click the BeanInfo tab.

  2. Specify an additional setting you need, such as the name of image files used for the icon you want your bean to use.

  3. Click the Generate Bean Info button.

JBuilder creates a BeanInfo class for your bean and displays it in the Component Tree. To see the code JBuilder generated for you, click the Source tab.

You can add any modifications you'd like to make directly in the source code, or you can make your changes using BeansExpress and regenerate the file again. After asking you if you want to replace the existing file, JBuilder regenerates the BeanInfo class.

For complete information about using BeansExpress including generating BeanInfo classes, see Creating JavaBeans with BeansExpress.