Java Alley | JavaBeans from Square One (Web Techniques, Sep 1997)



January 01, 2002
URL:http://www.drdobbs.com/java-alley-javabeans-from-square-one-we/184414287


//: Frog.java
// A trivial Java bean
import java.awt.*;
import java.awt.event.*;
class Spots {}
public class Frog {
  private int jumps;
  private Color color;
  private Spots spots;
  private boolean jmpr;
  public int getJumps() { return jumps; }
  public void setJumps(int newJumps) { 
    jumps = newJumps;
  }
  public Color getColor() { return color; }
  public void setColor(Color newColor) { 
    color = newColor; 
  }
  public Spots getSpots() { return spots; }
  public void setSpots(Spots newSpots) {
    spots = newSpots; 
  }
  public boolean isJumper() { return jmpr; }
  public void setJumper(boolean j) { jmpr = j; }
  public void addActionListener(
      ActionListener l) {
    //...
  }
  public void removeActionListener(
      ActionListener l) {
    // ...
  }
  public void addKeyListener(KeyListener l) {
    // ...
  }
  public void removeKeyListener(KeyListener l) {
    // ...
  }
  // An "ordinary" public method:
  public void croak() {
    System.out.println("Ribbet!");
  }

//: BeanDumper.java
// A method to introspect a bean
import java.beans.*;
import java.lang.reflect.*;
public class BeanDumper {
  public static void dump(Class bean){
    BeanInfo bi = null;
    try {
      bi = Introspector.getBeanInfo(
        bean, java.lang.Object.class);
    } catch(IntrospectionException ex) {
      System.out.println("Couldn't introspect " +
        bean.getName());
      System.exit(1);
    }
    PropertyDescriptor properties[] = 
      bi.getPropertyDescriptors();
    for(int i = 0; i < properties.length; i++) {
      Class p = properties[i].getPropertyType();
      System.out.println(
        "Property type:\n  " + p.getName());
      System.out.println(
        "Property name:\n  " + 
        properties[i].getName());
      Method readMethod = 
        properties[i].getReadMethod();
      if(readMethod != null)
        System.out.println(
          "Read method:\n  " + 
          readMethod.toString());
      Method writeMethod = 
        properties[i].getWriteMethod();
      if(writeMethod != null)
        System.out.println(
          "Write method:\n  " +
          writeMethod.toString());
      System.out.println("====================");
    }
    System.out.println("Public methods:");
    MethodDescriptor methods[] =
      bi.getMethodDescriptors();
    for(int i = 0; i < methods.length; i++)
      System.out.println(
        methods[i].getMethod().toString());
    System.out.println("======================");
    System.out.println("Event support:");
    EventSetDescriptor events[] = 
      bi.getEventSetDescriptors();
    for(int i = 0; i < events.length; i++) {
      System.out.println("Listener type:\n  " +
        events[i].getListenerType().getName());
      Method lm[] = 
        events[i].getListenerMethods();
      for(int j = 0; j < lm.length; j++)
        System.out.println(
          "Listener method:\n  " +
          lm[j].getName());
      MethodDescriptor lmd[] = 
        events[i].getListenerMethodDescriptors();
      for(int j = 0; j < lmd.length; j++)
        System.out.println(
          "Method descriptor:\n  " +
          lmd[j].getMethod().toString());
      Method addListener = 
        events[i].getAddListenerMethod();
      System.out.println(
          "Add Listener Method:\n  " +
        addListener.toString());
      Method removeListener =
        events[i].getRemoveListenerMethod();
      System.out.println(
        "Remove Listener Method:\n  " +
        removeListener.toString());
      System.out.println("====================");
    }
  }
  // Dump the class of your choice:
  public static void main(String args[]) {
    if(args.length < 1) {
      System.err.println("usage: \n" +
        "BeanDumper fully.qualified.class");
      System.exit(0);
    }
    Class c = null;
    try {
      c = Class.forName(args[0]);
    } catch(ClassNotFoundException ex) \{
      System.err.println(
        "Couldn't find " + args[0]);
      System.exit(0);
    }
    dump(c);
  }
class name: Frog
Property type:
  Color
Property name:
  color
Read method:
  public Color getColor()
Write method:
  public void setColor(Color)
====================
Property type:
  Spots
Property name:
  spots
Read method:
  public Spots getSpots()
Write method:
  public void setSpots(Spots)
====================
Property type:
  boolean
Property name:
  jumper
Read method:
  public boolean isJumper()
Write method:
  public void setJumper(boolean)
====================
Property type:
  int
Property name:
  jumps
Read method:
  public int getJumps()
Write method:
  public void setJumps(int)
====================
Public methods:
public void setJumps(int)
public void croak()
public void removeActionListener(ActionListener)
public void addActionListener(ActionListener)
public int getJumps()
public void setColor(Color)
public void setSpots(Spots)
public void setJumper(boolean)
public boolean isJumper()
public void addKeyListener(KeyListener)
public Color getColor()
public void removeKeyListener(KeyListener)
public Spots getSpots()
======================
Event support:
Listener type:
  KeyListener
Listener method:
  keyTyped
Listener method:
  keyPressed
Listener method:
  keyReleased
Method descriptor:
  public void keyTyped(KeyEvent)
Method descriptor:
  public void keyPressed(KeyEvent)
Method descriptor:
  public void keyReleased(KeyEvent)
Add Listener Method:
  public void addKeyListener(KeyListener)
Remove Listener Method:
  public void removeKeyListener(KeyListener)
====================
Listener type:
  ActionListener
Listener method:
  actionPerformed
Method descriptor:
  public void actionPerformed(ActionEvent)
Add Listener Method:
  public void addActionListener(ActionListener)
Remove Listener Method:
  public void removeActionListener(ActionListener)



//: TestBangBean.java
// A graphical Bean
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
class BangBean extends Canvas 
    implements Serializable {
  private int xm, ym;
  private int cSize = 20; // Circle size
  private String text = "Bang!";
  private int fontSize = 48;
  private Color tColor = Color.red;
  private ActionListener actionListener;
  public BangBean() {
    addMouseListener(new ML());
    addMouseMotionListener(new MM());
  }
  public int getCircleSize() { return cSize; }
  public void setCircleSize(int newSize) {
    cSize = newSize;
  }
  public String getBangText() { return text; }
  public void setBangText(String newText) {
    text = newText;
  }
  public int getFontSize() { return fontSize; }
  public void setFontSize(int newSize) {
    fontSize = newSize;
  }
  public Color getTextColor() { return tColor; }
  public void setTextColor(Color newColor) {
    tColor = newColor;
  }
  public void paint(Graphics g) {
    g.setColor(Color.black);
    g.drawOval(xm - cSize/2, ym - cSize/2, 
      cSize, cSize);
  }
  // This is a unicast listener, which is
  // the simplest form of listener management:
  public void addActionListener (
      ActionListener l) 
        throws TooManyListenersException {
    if(actionListener != null)
      throw new TooManyListenersException();
    actionListener = l;
  }
  public void removeActionListener(
      ActionListener l) {
    actionListener = null;
  }
  class ML extends MouseAdapter {
    public void mousePressed(MouseEvent e) {
      Graphics g = getGraphics();
      g.setColor(tColor);
      g.setFont(
        new Font(
          "TimesRoman", Font.BOLD, fontSize));
      int width = 
        g.getFontMetrics().stringWidth(text);
      g.drawString(text, 
        (getSize().width - width) /2,
        getSize().height/2);
      g.dispose();
      // Call the listener's method:
      if(actionListener != null)
        actionListener.actionPerformed(
          new ActionEvent(BangBean.this,
            ActionEvent.ACTION_PERFORMED, null));
    }
  }
  class MM extends MouseMotionAdapter {
    public void mouseMoved(MouseEvent e) {
      xm = e.getX();
      ym = e.getY();
      repaint();
    }
  }
}
public class TestBangBean extends Frame {
  BangBean bb = new BangBean();
  public TestBangBean() \{
    setTitle("BangBean Test");
    addWindowListener(new WL());
    setSize(300,300);
    add(bb, BorderLayout.CENTER);
    try {
      bb.addActionListener(new BBL());
    } catch(TooManyListenersException e) {}
  }
  class WL extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
      System.exit(0);
    }
  }
  class BBL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("BangBean action");
    }
  }
  public static void main(String args[]) {
    Frame f = new TestBangBean();
    f.setVisible(true);
  }
    }

 "Java Alley"
by  Bruce Eckel
Web Techniques, September 1997

Web Techniques grants permission to use these listings for private or
commercial use provided that credit to Web Techniques and the author is
maintained within the comments of the source. For questions, contact
[email protected].

LISTING ONE


//: Frog.java
// A trivial Java bean
import java.awt.*;
import java.awt.event.*;
class Spots {}
public class Frog {
  private int jumps;
  private Color color;
  private Spots spots;
  private boolean jmpr;
  public int getJumps() { return jumps; }
  public void setJumps(int newJumps) { 
    jumps = newJumps;
  }
  public Color getColor() { return color; }
  public void setColor(Color newColor) { 
    color = newColor; 
  }
  public Spots getSpots() { return spots; }
  public void setSpots(Spots newSpots) {
    spots = newSpots; 
  }
  public boolean isJumper() { return jmpr; }
  public void setJumper(boolean j) { jmpr = j; }
  public void addActionListener(
      ActionListener l) {
    //...
  }
  public void removeActionListener(
      ActionListener l) {
    // ...
  }
  public void addKeyListener(KeyListener l) {
    // ...
  }
  public void removeKeyListener(KeyListener l) {
    // ...
  }
  // An "ordinary" public method:
  public void croak() {
    System.out.println("Ribbet!");
  }



LISTING TWO



//: BeanDumper.java
// A method to introspect a bean
import java.beans.*;
import java.lang.reflect.*;
public class BeanDumper {
  public static void dump(Class bean){
    BeanInfo bi = null;
    try {
      bi = Introspector.getBeanInfo(
        bean, java.lang.Object.class);
    } catch(IntrospectionException ex) {
      System.out.println("Couldn't introspect " +
        bean.getName());
      System.exit(1);
    }
    PropertyDescriptor properties[] = 
      bi.getPropertyDescriptors();
    for(int i = 0; i < properties.length; i++) {
      Class p = properties[i].getPropertyType();
      System.out.println(
        "Property type:\n  " + p.getName());
      System.out.println(
        "Property name:\n  " + 
        properties[i].getName());
      Method readMethod = 
        properties[i].getReadMethod();
      if(readMethod != null)
        System.out.println(
          "Read method:\n  " + 
          readMethod.toString());
      Method writeMethod = 
        properties[i].getWriteMethod();
      if(writeMethod != null)
        System.out.println(
          "Write method:\n  " +
          writeMethod.toString());
      System.out.println("====================");
    }
    System.out.println("Public methods:");
    MethodDescriptor methods[] =
      bi.getMethodDescriptors();
    for(int i = 0; i < methods.length; i++)
      System.out.println(
        methods[i].getMethod().toString());
    System.out.println("======================");
    System.out.println("Event support:");
    EventSetDescriptor events[] = 
      bi.getEventSetDescriptors();
    for(int i = 0; i < events.length; i++) {
      System.out.println("Listener type:\n  " +
        events[i].getListenerType().getName());
      Method lm[] = 
        events[i].getListenerMethods();
      for(int j = 0; j < lm.length; j++)
        System.out.println(
          "Listener method:\n  " +
          lm[j].getName());
      MethodDescriptor lmd[] = 
        events[i].getListenerMethodDescriptors();
      for(int j = 0; j < lmd.length; j++)
        System.out.println(
          "Method descriptor:\n  " +
          lmd[j].getMethod().toString());
      Method addListener = 
        events[i].getAddListenerMethod();
      System.out.println(
          "Add Listener Method:\n  " +
        addListener.toString());
      Method removeListener =
        events[i].getRemoveListenerMethod();
      System.out.println(
        "Remove Listener Method:\n  " +
        removeListener.toString());
      System.out.println("====================");
    }
  }
  // Dump the class of your choice:
  public static void main(String args[]) {
    if(args.length < 1) {
      System.err.println("usage: \n" +
        "BeanDumper fully.qualified.class");
      System.exit(0);
    }
    Class c = null;
    try {
      c = Class.forName(args[0]);
    } catch(ClassNotFoundException ex) \{
      System.err.println(
        "Couldn't find " + args[0]);
      System.exit(0);
    }
    dump(c);
  }



LISTING THREE



class name: Frog
Property type:
  Color
Property name:
  color
Read method:
  public Color getColor()
Write method:
  public void setColor(Color)
====================
Property type:
  Spots
Property name:
  spots
Read method:
  public Spots getSpots()
Write method:
  public void setSpots(Spots)
====================
Property type:
  boolean
Property name:
  jumper
Read method:
  public boolean isJumper()
Write method:
  public void setJumper(boolean)
====================
Property type:
  int
Property name:
  jumps
Read method:
  public int getJumps()
Write method:
  public void setJumps(int)
====================
Public methods:
public void setJumps(int)
public void croak()
public void removeActionListener(ActionListener)
public void addActionListener(ActionListener)
public int getJumps()
public void setColor(Color)
public void setSpots(Spots)
public void setJumper(boolean)
public boolean isJumper()
public void addKeyListener(KeyListener)
public Color getColor()
public void removeKeyListener(KeyListener)
public Spots getSpots()
======================
Event support:
Listener type:
  KeyListener
Listener method:
  keyTyped
Listener method:
  keyPressed
Listener method:
  keyReleased
Method descriptor:
  public void keyTyped(KeyEvent)
Method descriptor:
  public void keyPressed(KeyEvent)
Method descriptor:
  public void keyReleased(KeyEvent)
Add Listener Method:
  public void addKeyListener(KeyListener)
Remove Listener Method:
  public void removeKeyListener(KeyListener)
====================
Listener type:
  ActionListener
Listener method:
  actionPerformed
Method descriptor:
  public void actionPerformed(ActionEvent)
Add Listener Method:
  public void addActionListener(ActionListener)
Remove Listener Method:
  public void removeActionListener(ActionListener)



LISTING FOUR


//: TestBangBean.java
// A graphical Bean
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
class BangBean extends Canvas 
    implements Serializable {
  private int xm, ym;
  private int cSize = 20; // Circle size
  private String text = "Bang!";
  private int fontSize = 48;
  private Color tColor = Color.red;
  private ActionListener actionListener;
  public BangBean() {
    addMouseListener(new ML());
    addMouseMotionListener(new MM());
  }
  public int getCircleSize() { return cSize; }
  public void setCircleSize(int newSize) {
    cSize = newSize;
  }
  public String getBangText() { return text; }
  public void setBangText(String newText) {
    text = newText;
  }
  public int getFontSize() { return fontSize; }
  public void setFontSize(int newSize) {
    fontSize = newSize;
  }
  public Color getTextColor() { return tColor; }
  public void setTextColor(Color newColor) {
    tColor = newColor;
  }
  public void paint(Graphics g) {
    g.setColor(Color.black);
    g.drawOval(xm - cSize/2, ym - cSize/2, 
      cSize, cSize);
  }
  // This is a unicast listener, which is
  // the simplest form of listener management:
  public void addActionListener (
      ActionListener l) 
        throws TooManyListenersException {
    if(actionListener != null)
      throw new TooManyListenersException();
    actionListener = l;
  }
  public void removeActionListener(
      ActionListener l) {
    actionListener = null;
  }
  class ML extends MouseAdapter {
    public void mousePressed(MouseEvent e) {
      Graphics g = getGraphics();
      g.setColor(tColor);
      g.setFont(
        new Font(
          "TimesRoman", Font.BOLD, fontSize));
      int width = 
        g.getFontMetrics().stringWidth(text);
      g.drawString(text, 
        (getSize().width - width) /2,
        getSize().height/2);
      g.dispose();
      // Call the listener's method:
      if(actionListener != null)
        actionListener.actionPerformed(
          new ActionEvent(BangBean.this,
            ActionEvent.ACTION_PERFORMED, null));
    }
  }
  class MM extends MouseMotionAdapter {
    public void mouseMoved(MouseEvent e) {
      xm = e.getX();
      ym = e.getY();
      repaint();
    }
  }
}
public class TestBangBean extends Frame {
  BangBean bb = new BangBean();
  public TestBangBean() \{
    setTitle("BangBean Test");
    addWindowListener(new WL());
    setSize(300,300);
    add(bb, BorderLayout.CENTER);
    try {
      bb.addActionListener(new BBL());
    } catch(TooManyListenersException e) {}
  }
  class WL extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
      System.exit(0);
    }
  }
  class BBL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("BangBean action");
    }
  }
  public static void main(String args[]) {
    Frame f = new TestBangBean();
    f.setVisible(true);
  }
    }

Java Alley | JavaBeans from Square One (Web Techniques, Sep 1997)

JavaBeans from Square One

By Bruce Eckel

Java is invaluable for creating reusable pieces of code, and the most reusable unit of code is the class, which comprises a cohesive unit of characteristics and behaviors that can be reused, either via composition or through inheritance.

Inheritance and polymorphism are essential to object-oriented programming, but when you're putting together an application, what you really want is components that you can drop into your design like an electronic engineer places chips on a circuit board (or even, in the case of Java, onto a Web page). It seems, too, that there should be some way to accelerate this module-assembly style of programming.

Visual programming first became successful, very successful, with Microsoft's Visual Basic (VB), followed by Borland's Delphi (the primary inspiration for the JavaBeans design). The reason that visual programming tools have been so successful is that they dramatically speed up the process of building an application -- certainly the user interface -- but often other portions of the application as well. These programming tools represent components visually, which makes sense since they usually display some kind of visual component, like a button or text field. The visual representation is, in fact, often identical to the component's appearance in the running program. So part of visual programming involves dragging a component from a palette and dropping it onto a form -- the application-builder tool writes code as you do this -- and that code causes the component to be created in the running program.

Simply dropping the component onto a form is normally not enough to complete the program. Usually the component's characteristics need to be changed: color, font, what database it's connected to, and so on. Modifiable characteristics, or "properties," can be manipulated inside the application-builder tool, and when you create the program this configuration data is saved so that it can be rejuvenated when the program is started.

But an object is more than characteristics; it's also a set of behaviors. At design time, visual-component behaviors are represented by "events," or things that can happen to the component. Normally, you determine what will happen when an event occurs by tying code to it.

Here's the critical part: The application-builder tool can dynamically interrogate a component to find out what properties and events it supports. Once it knows what they are, it can display them, allowing you to change properties and saving the state when you build the program. Generally, double clicking on an event causes the application-builder tool to create a code body and ties it to that particular event. You simply write the code that executes when the event occurs.

Because the application-builder tool manages all these connection details, you can focus on the program's appearance and functionality.

What is a Bean?

After the dust settles, then, a component is really just a block of code, typically embodied in a class. The key issue is the ability of the application-builder tool to discover the properties and events for that component. To create a VB component, the programmer had to write a fairly complicated piece of code, following certain conventions to expose the properties and events. Delphi was a second-generation visual-programming tool actively designed around visual programming, making it much easier to create a visual component. JavaBeans, however, has brought the creation of visual components to its most advanced state, because a Bean is just a class. You needn't write any extra code or use special language extensions to make something a Bean. You simply modify your method name to indicate to the application-builder tool whether it is a property, an event, or just an ordinary method.

For a property named Xxx, for example, you typically create two methods: getxxx and setxxx. (The first letter is automatically decapitalized to produce the property name.) The type produced by the get function is the same as the type of the argument to the set function. The name of the property and the type for the get and set are not related.

You can use this approach for a Boolean property as well, but you may also use isxxx instead of getxxx. Ordinary Bean methods don't conform to this naming convention, but they're public.

Events use the "listener" approach: addFooBarListener(FooBarListener) and removeFooBarListener(FooBarListener) to handle a FooBarEvent. The built-in events and listeners will usually satisfy your needs, but you can also create your own.

(Most of the method-name changes that occurred between Java 1.0 and Java 1.1 had to do with adapting to the get and set naming convention, in order to make that particular component into a Bean.)

We can use these guidelines to create a very simple Bean; see Listing One. You can see that it's just a class. Normally, all your fields will be private, and accessible only through methods. Following the naming convention, the properties are jumps, color, spots, and jumper. Although the name of the internal identifier is the same as the name of the property in the first three cases, jumper shows that the property name does not force you to use any particular name for internal variables (or indeed, to even have any internal variables for that property).

This Bean handles ActionEvent and KeyEvent, based on the naming of the add and remove methods for the associated listener. Finally, the ordinary method croak() is still part of the Bean simply because it's a public method, not because it conforms to any naming scheme.

One of the most critical parts of the Bean scheme occurs when you drag a Bean off a palette and plop it down on a form. The application-builder tool must be able to create the Bean (which it can, if there's a default constructor) and then, without access to the Bean's source code, extract all the necessary information to create the property sheet and event handlers.

Part of the solution is already evident from an earlier column in this series ("Discovering Information at Run Time: Part Deux," March, 1997): Java 1.1 "reflection" allows all the methods of an anonymous class to be discovered. This solves the Bean problem without any extra language keywords like those required in other visual-programming languages. In fact, one of the prime reasons reflection was added to Java 1.1 was to support Beans (as well as object serialization and remote method invocation).

This might lead you to expect that the creator of the application-builder tool would have to reflect each Bean and hunt through its methods to find its properties and events. However, Java designers wanted to provide a standard interface for everyone to use, not only to make Beans simpler to use, but also to provide a standard gateway to the creation of more complex Beans. This interface is the Introspector class, and its most important method is the static getBeanInfo(). When you pass this method a Class handle, it fully interrogates that class and returns a BeanInfo object that can be dissected to find properties, methods, and events.

Normally, you won't care about any of this -- you'll probably get most of your Beans off the shelf, drag them onto your form, then configure their properties and write handlers for the events that interest you. However, using the Introspector to display information about a Bean is an interesting and educational exercise; see Listing Two.

BeanDumper.dump() is the method that does all the work. First, it tries to create a BeanInfo object; if successful, it calls the methods of BeanInfo that produce information about properties, methods, and events. Introspector.getBeanInfo()'s second argument tells the Introspector where to stop in the inheritance hierarchy. Here, it stops before it parses all the methods from Object, since we're not interested in them. For properties, getPropertyDescriptors() returns an array of PropertyDescriptors. For each PropertyDescriptor, you can call getPropertyType() to find the class of object passed in and out via the property methods. Then, you can extract the pseudonym for each property from the method names with getName(), the method for reading with getReadMethod(), and the method for writing with getWriteMethod(). These last two methods return a Method object that can actually be used to invoke the corresponding method on the object (this is part of reflection).

For the public methods (including the property methods), getMethodDescriptors() returns an array of MethodDescriptors: You can get the associated Method object for each and print out their names.

For the events, getEventSetDescriptors() returns an array of EventSetDescriptors that can be queried to find out the class of the listener, the methods of that listener class, and the add- and remove-listener methods. The BeanDumper program prints out all of this information.

If you invoke BeanDumper on the Frog class like this: java BeanDumper Frog, the output will look like Listing Three.

This reveals most of what the Introspector sees as it produces a BeanInfo object from your Bean. The property type and name are independent. The property name is decapitalized except when it begins with more than one capital letter in a row, and the method names (such as the read and write methods) are produced from a Method object that can be used to invoke the associated method on the object.

The public method list includes both methods associated with a property or event and those that are not, such as croak(). These are all the methods that you can call programmatically for a Bean, and the application-builder tool can list them while you're making method calls, to ease your task.

Finally, you can see that the events are fully parsed out into the listener, its methods and the add- and remove-listener methods. Basically, once you have the BeanInfo, you can find out everything of importance for the Bean. You can also call its methods, even though you don't have any other information except the object itself (again, a feature of reflection).

A More Sophisticated Bean

This next example is slightly more sophisticated, albeit frivolous. It's a canvas that draws a little circle around the mouse whenever it moves. When you press the mouse, the word "Bang!" appears in the middle of the screen, and an action listener is fired.

You can adjust the size of the circle as well as the color, size, and text of the word displayed when you press the mouse. A BangBean also has its own addActionListener() and removeActionListener(), so you can attach your own listener to be fired when the user clicks on the BangBean.

Listing Four shows how BangBean implements the Serializable interface. The application-builder tool can "pickle" all the information for the BangBean using serialization after the program designer has adjusted the property values. When the Bean is created as part of the running application, these pickled properties are restored so you get exactly what you designed.

All the fields are private; normally, access to a Bean is allowed only through methods, usually using the property scheme.

To avoid multithreading issues, addActionListener() is "unicast" -- it only notifies one listener when the event occurs -- and may therefore throw a TooManyListenersException. Normally, you'll use "multicast" events, which notify many listeners. In fact, you should expect all Beans to be run in a multithreaded environment; for more details, see Chapter 14 of "Thinking in Java."

Pressing the mouse puts the text in the middle of the BangBean; if the actionListener field is not null, its actionPerformed() is called. Whenever the mouse is moved, its new coordinates are captured and the canvas is repainted (erasing any text on the canvas).

TestBangBean creates a Frame and places a BangBean within it, attaching a simple ActionListener to the BangBean. Normally, of course, the application-builder tool would create most of the code that uses the Bean.

When you run the BangBean through BeanDumper, you'll notice many more properties and actions. That's because BangBean is inherited from Canvas, and Canvas is itself a Bean, so you see its properties and events as well.

More Complex Bean Support

Creating a Bean can be remarkably simple, but you aren't limited to what I've shown here. The JavaBeans design can scale to more complex situations. There are several ways to add sophistication with properties:

Indexing. Here, I've shown single properties, but it's also possible to represent multiple properties in an array. This is called an "indexed property." You simply provide the appropriate methods and the Introspector recognizes an indexed property so your application-builder tool can respond appropriately.

Binding. A "bound" property notifies other objects via a PropertyChangeEvent. The other objects can then change themselves based on the change to the Bean.

Constraining. Other objects can veto unacceptable changes to a constrained property. The other objects are notified using a PropertyChangeEvent, and they may throw a ProptertyVetoException to prevent the change from happening and to restore the old values.

There are also ways to customize the way your Bean is represented at design time:

It's also possible to turn "expert" mode on and off in all FeatureDescriptors to distinguish between basic features and more complicated ones.

Conclusion

The arrival of JavaBeans on the scene has not been without disruption. For Sun, supporting JavaBeans has meant changes to Java 1.0, such as the new 1.1 event model, plus adding some features such as serialization. However, these changes represent genuine improvements to Java technology and will insure that Java remains an important, strategic platform for years to come.

(Get the source code for this article here.)


Bruce is the author of Thinking in Java (freely available at www.EckelObjects.com/Eckel) from which this article is adapted, Thinking in C++ (Prentice-Hall, 1995), and C++ Inside & Out (Osborne/McGraw-Hill 1993). He is the C++ & Java track chair for the Software Development conference and provides seminars and design consulting in C++ and Java. He can be reached at [email protected].



Code Resources

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.