7.6 Implementing the Animator Using the Java Event Model

 < Day Day Up > 



7.6 Implementing the Animator Using the Java Event Model

We will now change the Animator to fix the race condition error in Exhibit 5 (Program7.4). This will be accomplished by implementing the Animator using the standard pattern used in the Java Event Model. Because the Java Event Model is a standard design pattern, the animator can be rewritten to work and to conform to its standards. Most of the changes are cosmetic, such as naming conventions and conventions on the objects to pass from the component to the interface; however, the approach to dealing with the deadlock condition introduced at the end of Section 7.5.4, while short, is substantive. Section 7.6.1 introduces the Java Event Model, and Section 7.6.2 shows how it is implemented in the animator. After the workings of the Java Event Model have been presented, an improvement to the model (called a MultiCaster) is provided in Section 7.7.

7.6.1 Java Event Model

The Java Event Model is a standard pattern used to implement dispatching of events. It is used in the Java AWT to implement components such as Buttons, and TextFields to connect the component with the objects that process the events for those components. Most programmers have used the Java Event Model at some point. For example, every time an ActionListener is added to a Button the Java Event Model is used to call the actionPerformed method when that Button is pressed. But, while most programmers have used the Java Event Model, many do not know how it works. This chapter explains how to implement it.

The Java Event Model is shown in Exhibit 11. What is striking is how similar diagrams in Figures 7.1 and 7.3 are. This similarity is intentional, as the Java Event Model is very similar to the structure of calling Drawable objects in the animator of Exhibit 5 (Program7.4). In fact, the only major differences between the Java Event Model and the animator in Exhibit 5 (Program7.4) are that the Java Event Model has a much more formal definition and the loop that sends the events to the listening objects in the Java Event Model fixes the race condition that exists in the animator. The Java Event Model is described in this section, using the JButton class to describe how it works. Section 7.6.2 then shows how to use it to implement the animator.

Exhibit 11: Java Event Model

start example

click to expand

end example

The Java Event consists of three parts: (1) an event source, which generates the events based on some stimulus from an external source; (2) a listener, which registers to receive the events when they occur; and (3) an event state object, which passes information about the event from the Event Source to the Listener. This relationship is illustrated in Exhibit 11.

One important aspect of the Java Event Model is the strict conventions for naming of the parts of the model. The type of event (for example, an action for the JButton) forms the basis for names of the parts of the model. The source of the events would be the JButton and is called the event source object. The listener interface that will receive the event must be named for the type of event followed by the qualifier "Listener." The JButton uses action events, so the listener is named "ActionListener." Objects that implement the listener interface are called event listener objects. When an event source sends an event to the listener by calling the listener's method, it sends an object containing information about the event, called an event state object. The event state object passed from the event source to the Listener objects must be named for the type of event followed by the qualifier "Event" — for the JButton, an "ActionEvent."

The methods in the event source (JButton) that add and remove the listeners must be the name of the listener with the prefixes "add" and "remove," as in addActionListener and removeActionListener for the JButton. If you look at the Java AWT, you will find that these conventions are followed and allow a programmer to quickly identify the purpose of each of these classes.

The parts of the Java Event model for the JButton are shown in the following code fragments. First, the listener and even class are defined, as shown in step 1.

  • Step 1. Definition of listener interface and event object:

     public class ActionEvent extend java.util.Event {   public ActionEvent(Object source) {     super(source);   } } public interface ActionListener extends java.util.EventListener {   public void actionPerformed(ActionEvent e) {   } } 

Note that other fields and information are defined in ActionEvent, but the information shown in step 1 is the basic information needed to define an event. Once the listener interface and event class are defined, the event source can be written. There are two parts to the event source object. The first allows listeners objects to register with the source object using add and remove methods. For an ActionListener, the code would look as follows:

  • Step 2. Registering objects with the event source:

     Private Vector actionListeners; public synchronized void addActionListener(ActionListener al) {   actionListeners.addElement(al); } public synchronized void removeActionListener(ActionListener al) {   actionListeners.removeElement(al); } 

Note that both of these routines are synchronized and so protect access to the ActionListeners Vector.

When an event occurs, normally from an external source, the processEvent method of the event source is called. The processEvent method creates an event state object and sends the event to all registered listeners by calling the appropriate method in the listener (step 2). For the JButton, this occurs when the mouse is clicked while in the button, causing the GUI thread to call the processEvent method for the JButton. The code to perform this dispatching action is given below.

  • Step 3. Dispatching the event:

     public void processEvent() {   ActionEvent ae = new ActionEvent(this);// create event   synchronized {// clone Vector     Vector v = actionListeners.clone();   }   Enumeration e = v.elements();   // dispatch event   while(e.hasMoreElements()) {     ((ActionListener)(e.nextElement())).actionPerformed(ae);   } } 

This is very similar to the animator paint method except that the addActionListener and removeActionListener calls are synchronized, and the vector of objects to send the event to has been cloned. This cloning of the vector is significant because it provides the mechanism for ensuring that the dispatching of events is safe and cannot result in deadlock.

Remember that the original problem with dispatching events is that the vector of listeners can be modified while the events are being dispatched. A simple solution to prevent the vector from being modified while the events are being dispatched is to synchronize all the methods that access the vector of listeners, but this solution can result in deadlock. In fact, any solution that tries to synchronize all the methods will never work.

Fortunately, trying to find a way to synchronize all the methods accessing the vector of listeners is not the problem that has to be solved; rather, we want to provide the dispatching function with a vector that cannot be changed (i.e., is immutable) while it is dispatching the events. The Java Event Model does this by creating a local copy of the vector in variable v that is not accessible outside of this method (because it is on the program stack, it is unique to this thread). This vector is never changed in the method and hence is immutable. Because this vector never changes it cannot result in a listener being called twice or not at all. Further, this thread must be synchronized only for the length of time it takes to clone the vector to ensure that an add or remove is not running while the copy is being made. Now the thread processing the event does not hold the lock on the event source object when calling the listener object and circular deadlock is not possible, so the solution is safe.

It should be noted that the code to process an event presented here is from the Java Beans Specification; however, the actual solution implemented in the Java AWT to make an immutable collection of listeners is not the same as presented here. The actual solution that is used (a MultiCaster object) still relies on the fact that an immutable collection used in dispatching the events is separate from the collection used when adding and removing items, thus building on the ideas presented here (see Section 7.7). The animator presented here is correct, however, and is much easier to understand, so it is used throughout the remainder of the text.

7.6.2 Implementing the Corrected Animator Component

The Java Event Model just explained is now used to implement the animator as a component that obeys all of the rules of the Java Event Model. First, the interface Drawable does not follow the standards and is renamed a DrawListener. The name of the method that is called (the draw method) is fine, but the parameter to it must be an Event. Finally, any listeners defined in the Java Event Model must extend the java.util.EventListener interface; therefore, the DrawListener interface is created to extend java.util.EventListener and has a draw method that takes a parameter of a DrawEvent. Exhibit 12 (Program7.5a) shows the DrawListener class. Note that java.util.EventListener is a tag interface and does not have any methods, much like the Serializable interface.

Exhibit 12: Program7.5a: DrawListener Class

start example

 import java.util.EventListener; public interface DrawListener extends java.util.EventListener {   public void draw(DrawEvent e); } 

end example

Next in the Java Event Model, all event classes must extend the class java.util.Event and must have a constructor that takes an object as a parameter and passes that object to its super class. The object that is passed is the event source object, so when an event is created its constructor must be called with the "this" object as a parameter. In the case of the DrawEvent object, another variable is also needed, the Graphics object, so it is included in the object and set from a parameter in the constructor. Because this Graphics object is now a part of the event, a getGraphics method is included to retrieve the object in the DrawListeners draw method. Note that no setGraphics method is included because the Graphics object can only be set in the constructor and cannot be changed after the DrawEvent is constructed (the event object is immutable). Because the Graphic object is no longer a parameter to the draw method in the DrawListeners, the draw methods must retrieve it before they can use it. So, the start of all draw methods will now have code to retrieve the Graphic:

   public void draw(DrawEvent de) {     Graphics g = de.getGraphics();   } 

The last changes to the animator to implement the Java Event Model are to synchronize the addDrawListener and removeDrawListener methods and to implement the correct the logic in the paint method given in Section 7.6.1. This results in the final Animator code given in Program7.5 (Exhibits 12 through 15).

Exhibit 15 (Program7.5d) uses this animator. Note that it is similar to Exhibit 15 (Program7.5d), except for such things as naming conventions and the need to get the Graphics object from the event and not the parameter. Often, programmers are intimidated by high concepts such as the Java Event Model and feel that they are unapproachable; however, if you can understand the operation of the animator you should be able to understand the Java Event Model.

One other caveat must be noted regarding the objects that are to be added to the animator. As is shown in Chapter 8, it is often necessary to save the animator with the object to be animated so that it can add and remove itself as the animation is run. This is particularly useful when using threads or other concurrent programming techniques where the object is responsible for its own behavior. When doing this, one might be tempted to allow the object to register itself with the animator in the constructor, such as in the following MoveBall constructor:

Exhibit 13: Program7.5b: DrawEvent Class

start example

 import java.util.*; import java.awt.*; public class DrawEvent extends EventObject {   Graphics myGraphics;   public DrawEvent(Object source, Graphics graphics) {     super(source);     myGraphics = graphics;   }   public Graphics getGraphics() {     return myGraphics;   } } 

end example

   public MoveBall(Animator animator, int direction, int myNumber) {     this.animator = animator;     animator.addDrawListener(this);     this.direction = direction;     this.myNumber = myNumber;   } 

The code in this constructor is unsafe as it contains a race condition. The race condition exists because the object is added to the animator at the beginning of the constructor, allowing the GUI thread to possibly call the draw method on this object before the constructor for the object has completed the constructor code. Information that might be needed by the animator, such as the variable's direction or myNumber, might not at that point be initialized, which could cause the program to fail. This problem becomes even more buried if the class is extended, in which case the child's constructor will have called the parent's constructor and been added to the animator without the child even being aware of the possible race condition. This situation is dangerous and will lead to a general rule that anything that can start or use external threads should be avoided in constructors. In the case of the animator, it is always better if the object is added to the animator outside of the constructor, either in the main, as shown in Exhibit 15 (Program7.5d), or in the run method in a thread.



 < Day Day Up > 



Creating Components. Object Oriented, Concurrent, and Distributed Computing in Java
The .NET Developers Guide to Directory Services Programming
ISBN: 849314992
EAN: 2147483647
Year: 2003
Pages: 162

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net