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? Suppose 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 were probably developed, however, before the JavaBeans specification was finalized; 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. The result of this is that 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
  6. Customize the class for your bean by filling in those parts that pertain to your bean with the necessary values.
For example, 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
{
  public BevelPanelBeanInfo() {

    // Basic Info : Class beanClass
    beanClass = BevelPanel.class;

    // Basic Info : Class customizerClass
    //customizerClass (NONE)

    // Event Info : boolean introspectEvents
    introspectEvents = true;

    // Event Info : String[][4] eventSetDescriptors
    //              EventSet, ListenerClass, AddMethod, RemoveMethod
    //eventSetDescriptors (INTROSPECTED)

    // Event Info : String[][] eventListenerMethods
    //eventListenerMethods (INTROSPECTED)

    // Event Info : int defaultEventIndex
    //defaultEventIndex (NONE)

    // Property Info : boolean introspectProperties
    //introspectProperties (WE WILL SPECIFY)

    // Property Info : String[][5] propertyDescriptors
    //                 Name, ShortDescription, ReadMethod, WriteMethod, Editor (optional)
    propertyDescriptors = new String[][] {
      {"background",  Res.getString(Res.BI_background), "getBackground", "setBackground"},
      {"bevelInner",  Res.getString(Res.BI_BevelPanel_bevelInner), "getBevelInner", "setBevelInner", "borland.jbcl.editors.BevelTypeEditor"}, // int
      {"bevelOuter",  Res.getString(Res.BI_BevelPanel_bevelOuter), "getBevelOuter", "setBevelOuter", "borland.jbcl.editors.BevelTypeEditor"}, // int
      {"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"},
      {"soft",        Res.getString(Res.BI_BevelPanel_soft), "isSoft", "setSoft"},
      {"visible",     Res.getString(Res.BI_visible), "isVisible", "setVisible"},
    };

    // Property Info : int defaultPropertyIndex
    //defaultPropertyIndex (NONE)

    // Method Info : boolean introspectMethods
    introspectMethods = true;

    // Method Info : String[] methodNames
    //methodNames (INTROSPECTED)

    // Method Info : String[][] methodParameters
    //methodParameters (INTROSPECTED)

    // Icon Info : Image iconColor16x16
    //iconColor16x16 (NONE)

    // Icon Info : Image iconColor32x32
    //iconColor32x32 (NONE)

    // Icon Info : Image iconMono16x16
    //iconMono16x16 (NONE)

    // Icon Info : Image iconMono32x32
    //iconMono32x32 (NONE)

    // Additional Info : BeanInfo[] additionalBeanInfo
    //additionalBeanInfo (NONE)
  }
}
You might notice that BevelPanelBeanInfo marks as comments code elements that BevelPanel doesn't need, while BasicBeanInfo specifies null or true or false for many values. Either method of specifying explicit information that isn't provided is acceptable.

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 or not.

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:
   // Property Info : String[][4] propertyDescriptors
   //                 Name, ReadMethod, WriteMethod, Editor (optional)
    propertyDescriptors = new String[][] {
      {"background", "getBackground", "setBackground"},
      {"bevelInner", "getBevelInner", "setBevelInner", "borland.jbcl.editors.BevelTypeEditor"}, // int
      {"bevelOuter", "getBevelOuter", "setBevelOuter", "borland.jbcl.editors.BevelTypeEditor"}, // int
      {"enabled", "isEnabled", "setEnabled"},
      {"font", "getFont", "setFont"},
      {"foreground", "getForeground", "setForeground"},
      {"layout", "getLayout", "setLayout"},
      {"margins", "getMargins", "setMargins"},
      {"soft", "isSoft", "setSoft"},
      {"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.

Specifying a component image for the Component Palette

You can specify an image or icon to represent your component on the Component Palette using the component's BeanInfo class. These lines of code come from BasicBeanInfo:
protected Image iconColor16x16 = null;
protected Image iconColor32x32 = null;
protected Image iconMono16x16 = null
protected Image iconMono32x32 = null;
Replace the appropriate null with the name of the icon you want to use for your component. For example,
protected Image iconColor32x32 = "snazzyComp.gif";
You can also use the Palette Properties dialog box to to specify an image for your component.

Preparing to use a modified or new BeanInfo class

Whether you modified an exiting BeanInfo class or wrote a new one, you must exit JBuilder and then restart JBuilder to see the changes you made reflected in JBuilder's user interface.