Java Beans: Continued

Remember: Beans Architecture

A bean consists of and "exports" three things:

Events

We will do events next since
  1. we already know something about them (the delegation-event model of AWT)
  2. we are on the topic anyway given the events described in the properties section
  3. to create a Bean, you need to determine what it should do and then define the events, properties, and methods to get it there. Since, in actuality, most of the method definitions fall out of the definition of events and properties for the Bean, doing events next makes sense.

Beans are really no different than any other java class, one simply takes advantage of all of the capabilities classes have been allowed. For example, a bean can generate events in the same way that an AWT component, such as Button generates ActionEvent events. Events are simply for notifying others when something is happening; we have always had the capability to customize events by inheriting from the class java.util.EventObject

The delegation-event model of AWT, introduced with Java 1.1, demonstrates the Beans event model (as discussed earlier). It has three parts:

Basically, a Bean that wants to generate events needs a way to keep track of interested event targets. In the delegation event model, the event mechanism is broken up conceptually into event dispatch and event handling. Event dispatch is the responsibility of the event source; event handling is the responsibility of the event listener. Any object that wants to know when an event is fired by a bean can tell the bean it wants to be informed about particular events. In other words, an event listener registers interest in an event by calling a predetermined method in the event source.

Anyone can register an EventListener with a Component, provided the Component understands the event set. (For instance, you cannot register an ActionListener with a TextArea, but you can with a TextField). When something happens within the Component, it notifies any listener(s) by sending each an EventObject, through the appropriate method of the listener.

Consider the previous examples:

Remember VetoableChangeListener and its vetoableChange(PropertyChangeEvent evt) method? In this event handler we had a dialog box come up that said the option was not allowed. Remember the Voter bean in the examples in the BeanBox? Here, by default it rejected all vetoableChange requests, though you could change its vetoAll property to "true" and it would allow changes. For your own defined events and event handlers, you can allow whatever you want to allow.

Custom event

A bean defines an event if it provides methods for adding and removing event listener objects from a list of interested listeners for that event.

Once again, patterns are used in the signature of the method names. The pattern of the method's signature is detected by Java's new introspection mechanism, which can tell what events the source will generate from the name of the registration methods, together with the type of the arguments of the registration methods. For a detailed explanation see the Beans Spec, section 6.5 Event Listener Registration. (And some changes at Getting Listeners from JavaBeans)

Event Naming conventions

( Nutshell )

The general pattern for event generation capabilities recognized by Java's introspection mechanism is as follows:

If the button bean wants to be an event source, it must provide two methods that can be called by interested objects. One method adds the caller to the list of listeners who are notified when the event occurs. The other method removes the caller from the list of interested listeners.

where TYPE is replaced by the class name of the particular event listener.

The bean button needs a way to keep track of all of the listeners who might register to receive notification of the TYPE events. In our previous examples we were given the PropertyChangeSupport and VetoableChangeSupport classes). If we customize our events, we will need to implement these ourselves.

Since TYPE is vague, before I go on, let us use an example from one of the tutorials ... from the start. Remember, we have events, listeners, and objects that fire the events.

EventObject

The java.util.EventObject class is the basis of all Beans events.
public class java.util.EventObject
    extends Object implements java.io.Serializable {
  public java.util.EventObject (Object source);
  public Object getSource();
  public String toString();
}
Although you can create EventObject instances directly for your Bean events, design pattern guidelines require you to subclass EventObject so you have a specific event type. For example, to define an event for an employee's hire date, you could create a HireEvent class.
public class HireEvent extends EventObject {
  private long hireDate;
  public HireEvent (Object source) {
    super (source);
    hireDate = System.currentTimeMillis();
  }
  public HireEvent (Object source, long hired) {
    super (source);
    hireDate = hired;
  }
  public long getHireDate () {
    return hireDate;
  }
}

Notice the call to instantiate super so variables will be set. Also note, the event notification method has no way of distinguishing between event instances. It is up to the event object to provide the information that uniquely describes the specific instance of an event. The subclass encapsulates the information related to the event, and provides any methods required to access and manipulate that data.

Notice also the read-only properties of EventObjects

EventListener

In order to fire an event, the event source (or its support class) would have to know what listener method to call. This information is contained in the event-listener interface that defines the methods called to fire an event notification. Any class that wants to receive notification of the event would implement this interface. All event-listener interfaces inherit from the EventListener interface. This interface is empty, but serves as a tagging interface that all event listeners must extend; that way, Beans integration tools can work. A listener is something that desires notification when an event happens. The listener receives the specific EventObject subclass as a parameter. The name of the new listener interface is EventTypeListener. For the new HireEvent, the listener name would be HireListener. The actual method names within the interface have no specific design pattern to follow, but, should describe the event that is happening.
public interface HireListener
    extends java.util.EventListener {
  public abstract void hired (HireEvent e);
}
The method signature for an event-listener method is
void eventOccurranceMethodName (EventObjectType evt)
You can also include a throws clause that lists any checked exceptions that might be thrown when this method is invoked.

Event Source

Without an event source, the HireEvent and HireListener are virtually useless. The event source defines when and where an event will happen. Classes register themselves as interested in the event, and they receive notification when the event happens. A series of methods patterns represents the registration process:
public synchronized void addListenerType(ListenerType l);
public synchronized void removeListenerType(ListenerType l);
Multiple listeners can be registered with a source for a given set of events. When an event occurs it will be fired to all of the event-listener objects. For our own defined EventObjects, the event source needs to maintain the list of listeners itself (i.e., we need to supply the Support class). This is called multicast event delivery. The entire code to do this is:
private Vector hireListeners = new Vector();
public synchronized void addHireListener (HireListener l) {
  hireListeners.addElement (l);
}
public synchronized void removeHireListener (HireListener l) {
  hireListeners.removeElement (l);
}

If you are using AWT components, AWT events already have this behavior. Maintaining listeners is only necessary for new event types, or adding listeners where they previously were not (ActionEvent within a Canvas). Note that the JavaBeans specification does not dictate the event firing order for multicast event sources. It is up to the implementor of the source object to decide the order of notification.

The same holds true if an event listener is registered more than once, or if a non-existent event listener is unregistered. What happens is implementation dependent and should be documented. The ultimate user should know if it will be ignored, if an exception will be thrown, etc., so they can deal with it.

For example, if you want to permit only one listener (unicast), you have the addListenerType method throw the java.util.TooManyListenersException exception when adding a second listener(and you do not need a Vector).

OK, once the event happens, it is necessary for the event source to notify all the listeners. For the hiring example, this would translate into a method like the following:

protected void notifyHired () {
  Vector l;
  // Create Event
  HireEvent h = new HireEvent (this);
  // Copy listener vector so it won't change while firing
  synchronized (this) {
    l = (Vector)hireListeners.clone();
  }
  for (int i=0;i<l.size();i++) {
    HireListener hl = (HireListener)l.elementAt (i);
    hl.hired(h);
  }
}
You have to call the method directly when the triggering event happens.

Synchronization

Finally, remember to synchronize the add/remove methods. You cannot assume that the event notification is being delivered on the same thread that created your listening object. In fact, you should assume that this is not the case. Under these conditions, it is necessary for you to protect your code from multithreaded access by using the synchronization constructs

(Cloning:)

Don't forget to look at other events and listeners in the AWT package java.awt.event so that you can extend and enhance the provided interfaces in your code. An example is seen in "Java in a Nutshell", page 184 with their

This bean is very interesting - look at the code. Control trace:

  1. The user hits the button (note button's creation to see how it is defined)
  2. ActionListener witnesses and calls actionPerformed(ActionEvent e). Notice how type maps to AnswerEvent "constant" variables.
  3. fireEvent method creates a new AnswerEvent and fires away
  4. which means that the Listeners are cloned and each sent to its AnswerListener
  5. The AnswerListener then does whatever you have implemented it to do
The only problem I have with this Bean is, if it inherits from Object why doesn't it have implements Serializable? In fact, I have not gotten it into the Beanbox, but have opened it as an application via
java oreilly.beans.yesno.YesNoDialog
and get this

Now, another example...which does implements Serializable and I don't know how it can without doing something additional. Why? Is it because there is really nothing there you would save so any instance of this class would have been transient anyway? What about the instance of Thread?... And besides, it does say implements Serializable. Seems to me that any class using this would have to get the instance of it running again after it reads the Objects back. So it is the problem of the containing class? To say it implements Serializable indicates to me that Thread should... and it doesn't. Hmmm, any suggestions? thoughts?

At any rate, the Core Java book had great examples. In this case we want to look at

  1. the TimerBean with methods addTimerListener and removeTimerListener ( new(er) version - notice removal of thread stop)
  2. the TimerEvent class the custom event generated by this bean
  3. the TimerListener class with the notification method called timeElapsed
Now, do this: Pretty darn cool.

Demultiplexing and Event Adapters "Developing Java Bean", Englander

Because a listener object can receive event notifications from multiple sources, and each of these might want different actions to be performed, it is a good idea to be a multiplexing event receiver. Also, we might find it appropriate to have different methods in a class to deal with events from different instances of the class, so if we have one Listener used by all event sources, it needs to have some identification of these sources. Adapters are sometimes a good technique for decoupling an event listener from an event source. Some ways to deal with interface(s) and a lot of objects using it (so far):

Well, this is kind of out of "order", but one technique to have a "Generic Adapter" as shown in Developing Java Beans (pg 39) and uses the introspection ideas. So, as a precursor to the section on Introspection, here is some interesting information to provide the capabilities desired here. For background, see page 182 in Core Java V1.

Using Java Reflection

While your program is running, Java is always maintaining what is called run-time type identification (RTTI) on all objects. This information keeps track of the class to which each class belongs. One use of this information is to select the correct methods at run-time.

We, the everyday programmer, can access this information as well by using the "metaclass" of java.lang.Class. Specifically, this class is a class that is used to get information about classes. Each Object (classes and instances) used in a given program has a corresponding instance of java.lang.Class associated with it.

For example, the java.lang.Class class contains a static method called forName(). This method takes a string parameter containing the fully qualified class name and returns an instance of java.lang.Class. If the class does not exist, the exception java.lang.ClassNotFoundException will be thrown.

You can obtain the Class object associated with any Java object that is already instantiated. The Object class provides a method getClass(), which returns the associated instance of java.lang.Class An easy way to get a Class object is to assign the static definition to the class you are looking at. You can do this by appending .class to the class name Once we have the Class object, we can get an instance of java.lang.reflect.Method for any of the methods implemented by the object. The java.lang.Class contains a method called getMethod() which takes the name of the method as a String as one parameter, and an array of Class objects as the other parameter. This array contains the instances of java.lang.Class for each parameter in the method signature. If there are no parameters, you can pass null in place of the array.

Finally, the java.lang.reflect.Method class has a method named invoke() which is used to invoke a method. It takes the target Object as the first parameter, and an Object array as the second parameter. This parameter is used to pass the parameters to the method being invoked. (If a parameter is not an Object, you can use the fact that each primitive data type has a wrapper class with .TYPE static field. Example: float is the value of java.lang.Float.TYPE.

Example: We will use: the API Class getMethod and API Button setLabel and the API Method invoke

try
{
  // get the button class
  java.awt.Button theButton =new java.awt.Button("Sample");
  Class theClass = theButton.getClass();

  //get the string parameter class
  java.lang.Class paramClasses[] ={java.lang.String.class};

  // get the method 

  java.lang.reflect.Method mthd=theClass.getMethod("setLabel", paramClasses);

  //now invoke it, changing the label to "Did It"
  Object param[] = {new String("Did It")};

  mthd.invoke(theButton, param);
}
  catch (java.lang.NoSuchMethodException e) {}
  catch (java.lang.IllegalAccessException e) {}
  catch (java.lang.IllegalArgumentException e) {}  
  catch (java.lang.reflect.InvocationTargetException e) {} 

This technique allows us to create an adaptor that uses reflection to find the method that handles events. Now, we can use the technique to create an adaptor that adapts the interface to any arbitrary target object, and that can handle multiple object instances as event sources.

The example provided is one with object sources Thermometer and we want a GenericTemperatureAdaptor to implement TempChangeListener for the event type of TempChangedEvents.

The only requirement is that the signature of the methods that the adaptor will forward events to looks like:

The only new method needed for the GenericTemperatureAdaptor class is a registerEventHandler(); it takes a Thermometer object and a method name as parameters. This method is used to associate the event-handling method on the target object with the specific Thermometer event source.

When the adaptor receives a TempChangedEvent it will forward it to the method on the target with the name passed into this parameter. The adaptor uses a hash table to store these mappings.

See the Generic Temperature Adapter and the Thermometer class definitions. Interesting, eh?!

Note that they did not handle all of the exceptions that could be thrown. Memory trace...how does one know when an exception is thrown by a class?

Pretty spiffy. I apologize for yelling about Java not having a way to pass methods (although I read somewhere that the developers thought such a thing was a bad idea...they thought that use of interfaces was a better technique, and that this capability was a by-product of having the Reflection package available). I, for one, have found this capability useful in past languages, as well as the capability to determine all of the instances of a class. OK, another example:

The same technique can be done with classes supplied by Java in the AWT package (e.g., Buttons, ActionEvents and ActionListeners). The following is an adaptor that will handle the ActionListener interface for multiple buttons, and will forward the ActionEvent to a method on the target object. Here's the code for the GenericButtonAdapter and an example applet using it. The applet has three buttons, each with different handler methods exampleapplet2.java

This also leads to a nice place for a little more discussion on serialization, since this class uses a hash table. Since we didn't discuss this in the section on serialization, I want to take a few minutes here, as not to detract from this page, see this page.


Methods

Methods are operations for others to interact with a Bean. Basically, anything can call any public method of a Bean; usually the set of public methods defined by the class will map directly to the supported methods for the Bean. Beans receive notification of events by having the appropriate method called on them by the event source.

Besides all the methods required for each property and event of a Bean, you usually create support methods which accept no arguments or an argument of an event you listen for. That way, a builder application can inspect a class quickly for a list of the appropriate methods it can connect to. Non-public support methods may also be available. However, public methods with other parameter patterns are usually the result of a bad design pattern, and not easily connectable from a builder application. For instance, if you always pass along a time-stamp with an event, you should probably subclass the event, add a time-stamp attribute, and pass the subclass around. This results in a simplified design and easier maintainability.

As stated, bean methods are available for anyone to call by just making each public. However, you can restrict which methods are visible to the Bean builder/integration tool by providing a getMethodDescriptors method along with your Bean's BeanInfo. Every Bean can provide a supporting BeanInfo class to customize a Bean's appearance to an integration tool. More on BeanInfo