- Handling Bean
The concept of an event lies at the heart of almost every graphical computing environment, and JavaBeans is no exception. In JavaBeans, events are used to communicate information about the changing state of a bean or user interactions with a bean. Events form a core component of the JavaBeans architecture in that they are largely responsible for enabling beans to be plugged together as building blocks in a parent application. JavaBeans inherits its event-handling mechanism directly from the event-handling mechanism built into the standard Java 1.1 Application Programming Interface (API). The primary goal of the event-handling facilities in the Java 1.1 API is to provide a powerful and extensible means of propagating event information from components (beans) to applications and other components (beans).
Java 1.1 event handling revolves largely around the concepts of event sources and listeners. Beans that generate event notifications are considered event sources, whereas applications and beans that respond to events are considered to be event listeners. Event listeners are connected to event sources via an event registration mechanism provided by the standard Java 1.1 event-handling facilities. In this chapter you learn a great deal about event sources and listeners and why they are so important to event handling and JavaBeans. You also learn about event state objects, which represent information associated with an event, and event adapters, which make responding to events a little less cumbersome.
You learn the following major issues in this chapter:
- Event basics
- Event state objects
- Event listeners
- Event sources
- Event adapters
- Delivering events
- API support for events
The event-handling mechanism that JavaBeans uses is directly based on the built-in event-handling facilities provided by the Java 1.1 API. The event-handling approach taken by the Java 1.1 API is very different than the one used in the Java 1.0 API. Because the Java 1.1 API event-handling solution is relatively new, I'll go ahead and cover some of the fundamental issues behind it. You are probably already familiar with the event-handling mechanism employed by the Java 1.0 API, but it's important for you to understand how Java 1.1 deals with events. Like the persistence support used by JavaBeans, the event-handling support is directly provided by the Java 1.1 API.
In the Java 1.1 API, the event-handling portion of an application is completely separate from the application-specific code. This is starkly different from how the Java 1.0 API deals with events. Java 1.1 pulls off this feat by establishing different event types; each type of event is given a unique type class that is derived from a common root event class. The Java 1.1 API also introduces the concepts of event sources and listeners, which act as the sending and receiving ends of an event notification, respectively. Event sources are capable of generating events, whereas event listeners are designed to respond to events. An event is propagated from an event source to an event listener when the event source invokes a method on the listener and passes an object of the event type generated. Although beans typically play the role of event sources, they are fully capable of being listeners, as well.
Event listeners are connected to event sources through a registration mechanism that requires a listener to call a registration method on a source. When the registration method is called, the source is informed to send event notifications to the listener. Along with the capability of being registered with a source, listeners can also be unregistered, or removed as listeners from a source. Event sources are required to support the addition and removal of event listeners through a pair of public event registration methods. You learned a great deal about the syntax of event registration methods in Chapter 5, "Introspection: Getting to Know a Bean."
When an event occurs within an event source, the source sends an event notification to any listeners that have been registered. The event notification is sent via a method call on the listeners. Listeners are required to implement the generic EventListener interface, which is how sources are able to determine the types of registered listeners. Listeners define a method for each event to which they want to respond; these are the methods called by the source when one of the events occurs.
I mentioned the fact that multiple event listeners can be connected to a single source. Although most event sources support multiple listeners, some sources are designed in such a way as to enable only one listener at a time. This type of source is called a unicast event source, and its more liberal counterpart is called a multicast source. You learn more about unicast and multicast event sources later in the "Event Delivery" section of this chapter.
NOTE: This discussion of event sources and listeners might sound familiar to you. It turns out that the mechanism used by bound and constrained bean properties, about which you learned in Chapter 4, "Manipulating Bean Properties," is very similar to the event-handling mechanism involving sources and listeners.
In the Java 1.0 API, all of the events in the Java system are encapsulated in a single class called Event, and each different event is identified by a unique numeric identifier. The Java 1.1 API takes a very different approach by requiring each event to be implemented as a unique event class type. In other words, each event in the Java 1.1 API is a class within a hierarchy of event classes derived from a common root class, EventObject. With this approach it is possible for each event to have unique data and methods for accessing the data.
Actually, the Java 1.1 approach doesn't explicitly require every event type to map one-to-one to a unique class. It is acceptable to have a class represent a group of event types that are somehow related. For example, the MouseEvent class represents the event types corresponding to mouse moves, drags, and button clicks. In this case, the event data is the same for all of the events in the group, so it is perfectly reasonable to group them under one class. The different event types within an event group are differentiated by numeric identifiers, similar to the Java 1.0 approach.
One of the primary reasons for changing the event model to a hierarchy of classes in the Java 1.1 API was to enable the creation of user-defined events. This is a critical change in the core design of Java that was no doubt made to some extent because of JavaBeans. It would have been very difficult for JavaBeans to be as extensible as it is without this change to the event model.
The Java 1.1 API provides two conceptual types of standard events: low-level and semantic events. Low-level events are used to convey information about a low-level input or visual interface interaction such as a mouse drag, key press, or focus change. The following are the low-level events defined in the Java 1.1 API and how they are triggered for a bean:
- ComponentEvent--Triggered whenever the bean is resized, moved, hidden,
- FocusEven--Triggered whenever the bean receives or loses focus.
- InputEven--Never triggered directly; this event class serves as the
organizational event class for input events.
- KeyEven--Triggered whenever the bean receives a key press, key release,
or special key such as the Delete or Enter keys.
- MouseEven--Triggered whenever the bean receives a mouse button click, mouse button release, mouse move, or mouse drag.
New Term: Low-level events are events that convey information in response to a low-level input or visual interface interaction.
Semantic events convey information in response to high-level visual interface actions that relate to how a particular bean is used, as opposed to how it operates at a low level. For example, there is an adjustment event that corresponds to the value of a bean being adjusted; adjustable bean values are numeric values contained within a bounded range. The following are the semantic events defined in the Java 1.1 API and how they are triggered for a bean:
- ActionEvent--Triggered whenever a generic action occurs.
- AdjustmentEven--Triggered whenever a value is adjusted.
- ItemEven--Triggered whenever an item state changes.
New Term: Semantic events are events that correspond to high-level visual interface actions that are based more on the semantics of a particular bean.
Semantic events are different from low-level events in that they can apply across a variety of different beans that share a similar semantic usage. For example, buttons generate an action event when they are pressed, whereas menu items generate action events when they are selected. Additionally, a non-visual timer might generate an action event whenever its delayed time period expires.
Figure 6.1 shows how the low-level and semantic events fit into the Java 1.1 API
event hierarchy. Notice that the EventObject class is the common root class
for all events. Similarly, the AWTEvent class serves as the root for all
the standard Java 1.1 event classes, although other classes could certainly derive
directly from EventObject.
Figure 6.1. The Java 1.1 API event hierarchy.
NOTE: Throughout this section I've referred to the standard Java 1.1 API event-handling mechanism in terms of how it impacts beans. Actually, this mechanism is used throughout all of the Java 1.1 API, even in the absence of JavaBeans. In fact, due to the automatic nature of most of the JavaBeans facilities, standard Java 1.1 Advanced Windowing Toolkit (AWT) components technically qualify as beans themselves. The point is that there is often a blurring of functionality between JavaBeans and Java 1.1, as is the case with event handling.
The standard Java 1.1 API includes support for a variety of different event listeners. If you recall, event listeners provide a formal means of declaring which events can be caught by a particular application or bean. An event listener interface typically has an individual method for each different event type it is capable of catching and responding to. Related events are usually grouped together within a single listener interface. For example, the standard MouseListener event listener interface provides event response methods for the mouse entering and exiting a bean, as well as the mouse button being pressed and released over a bean.
Similar to the event classes about which you learned in the previous section, the event listener interfaces defined in the Java 1.1 API are divided into two different types: low-level and semantic. The low-level listener interfaces deal with events at a low level, such as mouse movement and bean focus changes. The following are the low-level event listener interfaces provided by the Java 1.1 API:
- ComponentListener--Listens for a bean being resized, moved, hidden,
- FocusListene--Listens for a bean focus change.
- KeyListene--Listens for a key press, key release, or special key such
as the Delete or Enter keys.
- MouseListene--Listens for mouse clicks and for the mouse entering and
exiting a bean.
- MouseMotionListene--Listens for mouse moves and drags.
- WindowListene--Listens for a window opening, closing, iconifying, or de-iconifying.
The semantic listener interfaces deal with events at a higher logical level, such as menu item selections. The following are the semantic event listener interfaces provided by the Java 1.1 API:
- ActionListener--Listens for an action.
- AdjustmentListene--Listens for a value adjustment.
- ItemListene--Listens for an item state change.
I haven't really clarified why the event listener interfaces in the Java 1.1 API are provided as interfaces rather than classes. The reason is that event listeners typically are used to provide some type of application-specific response to an event notification. This means that there really is no default behavior available for an event listener response method. Knowing this, you might think that there is no reason to even have any built-in event listener support. However, there still needs to be a standard, consistent mechanism for passing events from source to listener. Event listener interfaces provide that mechanism by defining the standard methods that are to be called by a source whenever an event occurs. Classes that must actually handle an event are required to implement an event listener interface and provide event response code in the appropriate listener method.
NOTE: Because most event listener interfaces provide a range of event response methods, you might be concerned about having to fully implement an interface. In other words, what happens if you want to respond to only one event, but the listener interface requires you to implement a handful of response methods? The Java 1.1 API provides event adapter classes that provide empty do-nothing versions of all the methods in an interface. Using an adapter class, you can easily override only the methods you need without worrying about the others. You learn more about adapters a little later in the "Event Adapters" section of this chapter.
Although the event listener interfaces are very important in the grand scheme of Java events, they provide only half of the event delivery mechanism in Java. The other half is handled by event sources, which actually generate event notifications. Event sources must provide a means for registering listeners, which is how sources know to which listeners to dispatch events whenever they occur. Whenever an event occurs, an event source must handle the details of examining its registered listeners and dispatching the event notifications.
Where the event listener methods easily distinguish event listeners from
other classes, the event listener registration methods distinguish event sources from other classes. Event sources are in fact required to implement a pair of registration methods for each type of event listener they support. This enables users and application builder tools to connect listeners to sources so that events can be successfully delivered. The event listener registration methods provide the wiring for the event handling mechanism in Java 1.1, and subsequently for JavaBeans.
One of the primary reasons for using event sources and listeners is to optimize event delivery. In Java 1.0, events are delivered to all objects defined as being able to receive a particular event. For example, when you drag the mouse over a button, the button is flooded with mouse move events. The source/listener approach promoted by Java 1.1 and used by JavaBeans is much more efficient because events are delivered only to registered listeners. In other words, no events are ever generated without an object explicitly being registered as an event listener. This explicit statement is made by an event listener when it is registered with an event source.
Similar to the other areas of Java event handling, a distinction is made among event sources by way of low-level and semantic events. The event source for low-level events usually is a visual component such as a button or menu. Low-level events are tightly bound to the physical event source that generates them. Basically, a low-level source implements a low-level event listener interface, whereas a semantic source implements a semantic event listener interface. The Java 1.1 API defines low-level event listeners on the following standard event source components:
These low-level sources are pretty logical because they form the core set of classes that model visual components. More high-level components implement the higher level semantic event listeners. The following are the standard event source components that implement semantic event listener interfaces in the Java 1.1 API:
One problem alluded to earlier in this chapter in regard to event listener interfaces is that they require a class to implement all the methods defined in the interface. Without implementing all the methods, the class would remain abstract and could not be used directly. This is not a problem in cases in which most or all of the event response methods defined in an interface are going to be used. But what about the opposite scenario, in which you want to respond to only a single event and the listener interface provides a variety of different response methods?
Because you must provide implementations for all the methods in the interface, you would have to add empty do-nothing methods for all the events you don't care about responding to, in addition to the event response method in which you are interested. This approach clearly is annoying, simply because you must add unnecessary code that is required only to adhere to the object-oriented underpinnings of the Java programming language.
There is a simple solution. The Java 1.1 API provides a set of event adapters that are convenience classes for supporting event listeners. "Convenience classes" means that event adapters enable you to implement only the event response methods you need, freeing you from the hassle of writing a bunch of useless empty methods. Keep in mind that event adapters are classes and not interfaces, which means that you must derive from them instead of implementing them. The fact that they are designed as classes is what enables you to selectively provide only the event response methods you need.
There is a unique event adapter class for each low-level event listener interface. Each event adapter implements all the methods in the corresponding event listener interface it is designed to support. You might be wondering why there are no event adapters for the semantic event listener interfaces. Well, if you look closely at the Java 1.1 API documentation, you will find that each semantic event listener interface defines only a single method. Because there is only one method in these interfaces, there is no benefit to providing an adapter class.
At this point, you might be wondering how the event adapters pull off the seemingly magical trickery of bypassing the limitation of having to fully implement an interface. Don't get too excited, because there is absolutely nothing magical about event adapters. Event adapters simply provide empty do-nothing methods for all the methods defined in an interface, just as you could provide in your own classes. As you learned earlier, event adapters are convenience classes, which means that they are there only as a convenience; they don't work miracles or do anything else other than save you a little bit of coding hassle. The following are the event adapter classes provided by the Java 1.1 API:
NOTE: Aside from just being a programming convenience, event adapters can also be used to provide customized delivery of events. In this scenario, a special event adapter is designed and positioned between an event source and event listener so that it can enforce some kind of specialized policy on the event delivery.
Perhaps the most important issue relating to JavaBeans and events is how events are actually delivered. You already understand the relationship between event sources, listeners, and state objects, but you still might not see the big picture in terms of how they fully relate to each other. On the other hand, maybe you do. If so, just bear with me a little because I'm going to hammer it home one more time. Along with covering the relationship between these functional elements, I also want to introduce a few other topics related to event delivery.
To recap a little, an event delivery consists of three functional parts: an event source, an event listener, and an event state object. The entire process begins when an event listener notifies a source that it wants to listen to a particular event. It does this by calling an event listener registration method provided by the source. When this happens, the source is ready to deliver events to the listener. Whenever an internal change occurs in the source that generates an event, the source creates an event state object describing the event and sends it out to the listener. The source does this by calling one of the event response methods defined in the listener's event listener interface.
NOTE: It's worth noting that event delivery is synchronous, meaning that an event is guaranteed complete delivery without interruption. However, there is no guarantee that event delivery across a group of listeners will occur within the same thread.
That summarizes the basic scenario surrounding an event delivery from an event source to an event listener. Now let's look at a few issues relating to this delivery process.
All of the standard Java 1.1 API components support multiple event listeners, which means that multiple event listeners can be registered with and receive events from a single event source. This type of event delivery is known as multicast delivery, because the event is being broadcast to multiple event listeners simultaneously. With multicast event delivery, event listeners can be added and removed at will via the event listener registration methods, and the source is responsible for keeping track of the current set of listeners. Keep in mind that the Java event delivery mechanism makes no guarantees about the order in which listeners receive events during a multicast delivery.
Although multicast event delivery is the most popular type of event delivery and shares the widest range of use, Java 1.1 also supports unicast event delivery, in which only one listener is permitted to listen to a source at a time. Unicast event sources can have only one registered listener at any given time. The event registration methods are designed so that an exception is thrown if additional listeners are attempted to be added to a unicast event source with a listener already registered. Even though none of the standard Java 1.1 API components are unicast sources, it is expected that some custom components (beans) will implement a unicast approach for one reason or another.
It's important to understand that unicast event delivery is a more limited form of event delivery and should be avoided whenever possible. In other words, if you are developing your own beans, make sure you have a very good reason for making them a unicast event source. Otherwise, stick with multicast and you will give users of your bean far more event delivery options.
Just so you get the picture of unicast and multicast event sources, Figures 6.2 and 6.3 show how each of them works in regard to the delivery of an event.
Figure 6.2 shows how an event is delivered from a unicast source to a single listener via an event state object. Figure 6.3 shows how an event is delivered from a multicast source to a group of listeners via an event state object. These two scenarios are very similar; the only difference is the number of listeners allowed to receive the event.
Figure 6.2. Unicast delivery of a bean event.
Figure 6.3. Multicast delivery of a bean event.
There are a few different problems that can arise when you are dealing with event delivery. It is important to understand these problems and how they impact event sources and listeners. One problem occurs when event response methods throw exceptions back at an event source. The problem is, how should an event source react to an event response method when the listener throws an exception? It doesn't seem like a listener method should be throwing exceptions at the source, but the situation can technically occur. Unfortunately, there are no hard and fast rules governing what should happen in a situation like this. Java 1.1 stipulates that the manner in which an event source deals with listener-generated exceptions is entirely implementation dependent, which means that you must consult the documentation for a particular source to find out what happens.
Another problem associated with event delivery occurs when the set of event listeners for a source is updated during a multicast event delivery. In this situation it is technically possible for a listener to be removed from the source before its event has been delivered. When this occurs, it is possible for an event to be delivered to a listener that is no longer registered, which certainly seems like a problem. Again, there unfortunately are no strict rules about how event sources should deal with this scenario. It is entirely implementation specific, so you might want to refer to the source documentation if you are worried about this problem creeping up on you.
The powerful event-handling mechanism about which you've learned in this chapter is made possible largely by a simple class and interface in the Java 1.1 API. The chapter finishes with a quick look at this class and interface, which follow respectively:
NOTE: This class and interface are both covered in detail in the API documentation that ships with Java 1.1. They are mentioned here because the JavaBeans approach to event handling is largely based on them.
The EventObject class represents information associated with a generic event. This class is designed as an abstract base class from which all event state classes are derived. The following additional event state classes are derived from EventObject and are provided in the standard Java 1.1 API:
NOTE: For more information on these event state classes, please refer to Appendix B, "JavaBeans API Quick Reference."
The EventListener interface defines the overhead required of an event listener. This class actually defines no methods, which means that it exists primarily as a means of identifying derived class types as event listeners. The following additional event listener interfaces are derived from EventListener and are provided in the standard Java 1.1 API:
NOTE: For more information on these event listener interfaces, please refer to Appendix B.
This chapter took you through one of the more core elements of JavaBeans: events. Events ultimately take on the responsibility of enabling beans to interact with each other, which is no small task. Fortunately, the event handling facilities in JavaBeans are borrowed directly from the standard event handling facilities in the core Java 1.1 API. This results in a leaner and more consistent design, which is not surprising considering the fact that JavaBeans is intended to alleviate much of the learning curve of other component technologies with minimal overhead.
In this chapter, you learned how event sources and listeners are used to manage the flow of event information between beans and interested parties. You also learned a great deal about how this mechanism works behind the scenes, which gave you greater insight into JavaBeans event handling in general. You moved on to learn some details about event delivery and some of the problems associated with it. You finished the chapter by learning about the specific parts of the Java 1.1 API that bring events to life.
With events safely behind you, you are now ready to push on into another area of JavaBeans: persistence. In Chapter 7, "Persistence: Saving Beans for a Rainy Day," you find out what persistence is all about and how it enables beans to be resurrected from the grave.