|
Event Listening and Event Bubbling
An Introduction with Examples in Java |
|
Prof. David Bernstein |
| Computer Science Department |
| bernstdh@jmu.edu |
Event Listening
Event Bubbling
One Simple Implementation
/**
* An encapsulation of an Event
*
* This implementation is generic so that the two
* variants, event listening and event bubbling, can
* have their own type of Event.
*
* The type parameter, S, is used to bind the type of the
* source. It can be either EventGenerator (for event
* listening) or EventHandler (for event bubbling).
*
* This class has package visibility to prevent it
* from being typed inappropriately in classes
* outside of this package.
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
class Event<S>
{
private S source;
private String name, value;
/**
* Explicit Value Constructor
*
* @param source The source of this Event
* @param name The name of this Event
* @param value The value of this Event
*/
public Event(S source, String name, String value)
{
this.source = source;
this.name = name;
this.value = value;
}
/**
* Get the source of this Event
*
* @return The source
*/
protected S getSource()
{
return source;
}
/**
* Get the name of this Event
*
* @return The name
*/
public String getName()
{
return name;
}
/**
* Get the value of this Event
*
* @return The value
*/
public String getValue()
{
return value;
}
}
import java.util.*;
/**
* The core functionality of all event queues
*
* This implementation is generic so that the two
* variants, ListenerEventQueue and BubblingEventQueue,
* can used different types of Event objects (ListenerEvent
* and BubblingEvent objects, respectively).
*
* The type parameter, E, is used to bind the type of the
* Event.
*
* This class has package visibility to prevent it
* from being typed inappropriately in classes
* outside of this package.
*
* Note: Concrete implementations of this class
* need to implement the fireEvent method.
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
abstract class EventQueue<E> implements Runnable
{
private volatile boolean keepRunning;
protected LinkedList<E> events;
private Thread controlThread;
private final Object lock = new Object();
/**
* Default Constructor
*/
protected EventQueue()
{
events = new LinkedList<E>();
}
/**
* Start the event queue
*/
public void start()
{
if (controlThread == null)
{
keepRunning = true;
controlThread = new Thread(this);
controlThread.start();
}
}
/**
* Stop the event queue (after all events have
* been processed)
*/
public void stopEventQueue()
{
synchronized(lock)
{
keepRunning = false;
controlThread.interrupt();
}
}
}
/**
* Fire an event
*
* Note: The code in this method does not need to be
* synchronized because it is protected and is only
* called by firePendingEvents() which is called by
* a synchronized block in run()
*
* @param event The event to fire
*/
protected abstract void fireEvent(E event);
/**
* Fire all pending events
*
* Note: The code in this method does not need to be
* synchronized because it is private and is only
* called by a synchronized block in run()
*/
private void firePendingEvents()
{
E event;
while (events.size() > 0)
{
event = events.removeFirst();
fireEvent(event);
}
}
/**
* The code to execute in the control thread
*/
public void run()
{
Event event;
while (keepRunning)
{
synchronized(lock)
{
firePendingEvents();
try
{
lock.wait();
}
catch (InterruptedException ie)
{
// stop() was called
}
}
}
controlThread = null;
}
/**
* Push an event onto the queue
*
* @param event The event to post
*/
protected void postEvent(E event)
{
synchronized(lock)
{
// Add an event to the queue
events.addLast(event);
// Start the processing
lock.notifyAll();
}
}
Overview
ListenerEvent
/**
* A simple encapsulation of an Event in an event
* listener system
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class ListenerEvent extends Event<EventGenerator>
{
/**
* Explicit Value Constructor
*
* @param source The source of this Event
* @param name The name of this Event
* @param value The value of this Event
*/
public ListenerEvent(EventGenerator source,
String name,
String value)
{
super(source, name, value);
}
}
EventListener
/**
* Requirements of an EventListener in an
* event listener system
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public interface EventListener extends EventReceiver
{
/**
* Handle an event
*
* @param event The Event to handle
* @return true if the Event was handled
*/
public abstract boolean handleEvent(ListenerEvent event);
}
ListenerEventQueue
import java.util.*;
/**
* An event queue with (different categories of) listeners
*
* Note: This implementation uses the Singleton pattern to ensure
* that there is at most one ListenerEventQueue
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class ListenerEventQueue extends
EventQueue<ListenerEvent>
{
}
private Hashtable<String,
Vector<EventListener>> categories;
/**
* Default Constructor
*/
private ListenerEventQueue()
{
super();
categories = new Hashtable<String,
Vector<EventListener>>();
}
/**
* Add an event listener
*
* Note: This method is not synchronized
* because Vector and Hashtable are thread safe
*
* @param listener The event listener to add
* @param category The category/name of the events to listen to
*/
public void addListener(EventListener listener,
String category)
{
Vector<EventListener> listeners;
// Get the Hashtable of listeners for this category
listeners = categories.get(category);
// If no such category exists, create one
if (listeners == null)
{
listeners = new Vector<EventListener>();
categories.put(category, listeners);
}
// Add this listener
listeners.add(listener);
}
/**
* Remove an event listener
*
* Note: This method is not synchronized
* because Vector and Hashtable are thread safe
*
* @param listener The event listener to remove
* @param category The category/name of the events
*/
protected void removeListener(EventListener listener,
String category)
{
Vector<EventListener> listeners;
// Get the Hashtable of listeners for this category
listeners = categories.get(category);
// If the category exists, remove the listener
if (listeners != null) listeners.remove(listener);
}
/**
* Fire an event
*
* Note: The code in this method does not need to be
* synchronized because it is protected and is only
* called by firePendingEvents() which is called by
* a synchronized block in run()
*
* @param event The event to fire
*/
protected void fireEvent(ListenerEvent event)
{
Enumeration<EventListener> e;
EventListener listener;
Vector<EventListener> listeners;
String category;
// The name of the event corresponds to the category
category = event.getName();
// Get the listeners for this category
listeners = categories.get(category);
// Inform all of the listeners (if there are any)
if (listeners != null)
{
e = listeners.elements();
while (e.hasMoreElements())
{
listener = e.nextElement();
listener.handleEvent(event);
}
}
}
// The instance used in the Singleton pattern
private static ListenerEventQueue instance;
/**
* Get the (Singleton) instance
*
* @return The ListenerEventQueue
*/
public static synchronized ListenerEventQueue getEventQueue()
{
if (instance == null) instance=new ListenerEventQueue();
return instance;
}
Overview
BubblingEvent
/**
* A simple encapsulation of an Event in an event bubbling
* system
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class BubblingEvent extends Event<EventHandler>
{
/**
* Explicit Value Constructor
*
* @param source The source (and receiver) of this Event
* @param name The name of this Event
* @param value The value of this Event
*/
public BubblingEvent(EventHandler source,
String name,
String value)
{
super(source, name, value);
}
}
EventHandler
/**
* Requirements of an EventHandler in an event bubbling
* system
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public interface EventHandler extends EventReceiver
{
/**
* Get the parent of this EventHandler
*
* @return The parent (or null)
*/
public abstract EventHandler getParent();
/**
* Handle an event
*
* @param event The event to handle
* @param bubbleHandledEvents true if handled events should be bubbled
* @return true if the event was handled
*/
public abstract boolean handleEvent(BubblingEvent event,
boolean bubbleHandledEvents);
}
/**
* An abstract EventHandler in an event bubbling system
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public abstract class AbstractEventHandler implements
EventGenerator,
EventHandler
{
/**
* Handle an event
*
* @param event The event to handle
* @param bubbleHandledEvents true if handled events should be bubbled
* @return true if the event was handled
*/
public boolean handleEvent(BubblingEvent event,
boolean bubbleHandledEvents)
{
boolean handled;
EventHandler parent;
// Try and handle the Event locally
handled = handleEvent(event);
// Bubble the Event as necessary
if (bubbleHandledEvents || !handled)
{
parent = getParent();
if (parent != null)
{
handled = parent.handleEvent(event,
bubbleHandledEvents);
}
}
return handled;
}
/**
* Handle an event
*
* @param event The event to handle
* @return true if the event was handled
*/
public abstract boolean handleEvent(BubblingEvent event);
}
BubblingEventQueue
import java.util.*;
/**
* An event queue with event bubbling
*
* Note: This implementation uses the Singleton pattern to ensure
* that there is at most one ListenerEventQueue
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class BubblingEventQueue extends EventQueue<BubblingEvent>
{
}
private Hashtable<String, Boolean> handlers;
// "Constants"
private static final Boolean F = new Boolean(false);
private static final Boolean T = new Boolean(true);
/**
* Default Constructor
*/
private BubblingEventQueue()
{
super();
handlers = new Hashtable<String, Boolean>();
}
/**
* Set whether a particular Event should be bubbled
* if it was already handled
*
* @param name The name of the Event
* @param bubbleIfHandled true to make this name bubble if handled
*/
public void setBubbleIfHandled(String name,
boolean bubbleIfHandled)
{
if (bubbleIfHandled) handlers.put(name, T);
else handlers.put(name, F);
}
/**
* Fire an event
*
* Note: The code in this method does not need to be
* synchronized because it is protected and is only
* called by firePendingEvents() which is called by
* a synchronized block in run()
*
* @param event The event to fire
*/
protected void fireEvent(BubblingEvent event)
{
boolean bubbleHandledEvents;
Boolean b;
EventHandler source;
source = event.getSource();
b = handlers.get(event.getName());
bubbleHandledEvents = false;
if (b != null) bubbleHandledEvents = b.booleanValue();
source.handleEvent(event, bubbleHandledEvents);
}
// The instance used in the Singleton pattern
private static BubblingEventQueue instance;
/**
* Get the (Singleton) instance
*
* @return The BubblingEventQueue
*/
public static synchronized BubblingEventQueue getEventQueue()
{
if (instance == null) instance = new BubblingEventQueue();
return instance;
}
/**
* Makes operators aware of messages
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class SituationBoard implements EventListener
{
private int messageCounter;
/**
* Default Constructor
*/
public SituationBoard()
{
messageCounter = 0;
}
/**
* Display a message on this SituationBoard
*
* For simplicity, this implementation just displays
* the message on the console
*
* @param name The name of the message
* @param value The message
*/
private void displayMessage(String name, String value)
{
System.out.println(messageCounter+":\t"+
name + "\t" + value + "\n");
}
/**
* Handle an event
*
* @param event The event to handle
* @return true if the event was handled
*/
public boolean handleEvent(ListenerEvent event)
{
String name, value;
name = event.getName();
value = event.getValue();
messageCounter++;
displayMessage(name, value);
return true;
}
}
import java.io.*;
/**
* Logs messages to a file
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class MessageLogger implements EventListener
{
FileWriter out;
String fileName;
/**
* Default Constructor
*/
public MessageLogger()
{
fileName = "log.txt";
}
/**
* Handle an event
*
* @param event The event to handle
* @return true if the event was handled
*/
public boolean handleEvent(ListenerEvent event)
{
String name, value;
name = event.getName();
value = event.getValue();
logMessage(name, value);
return true;
}
/**
* Log a message
*
* This method re-opens the file (for append) each time
* it is called to ensure that no messages are lost
*
* @param name The name of the message
* @param value The message
*/
private void logMessage(String name, String value)
{
try
{
out = new FileWriter(fileName, true);
out.write(name + "\t" + value + "\n");
out.close();
}
catch (IOException ioe)
{
System.out.println("Problem logging: " + name +
"\t" + value);
}
}
}
import java.util.*;
/**
* A simple MessageEntrySystem that can be used to
* demonstrate event listening
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class MessageEntrySystem implements EventGenerator
{
private ListenerEventQueue eventq;
private Scanner keyboard;
/**
* Default Constructor
*/
public MessageEntrySystem()
{
keyboard = new Scanner(System.in);
eventq = ListenerEventQueue.getEventQueue();
}
/**
* Start this MessageEntrySystem
*/
public void start()
{
ListenerEvent event;
String line, msg, name;
StringTokenizer st;
while (keyboard.hasNext())
{
line = keyboard.nextLine();
st = new StringTokenizer(line, "\t");
name = st.nextToken();
msg = st.nextToken();
event = new ListenerEvent(this, name, msg);
eventq.postEvent(event);
}
}
}
/**
* An example that uses event listening
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class ListeningDriver
{
/**
* The entry point of the application
*
* @param args The command-line arguments
*/
public static void main(String[] args)
{
ListenerEventQueue eventq;
SituationBoard board;
MessageEntrySystem entry;
MessageLogger logger;
logger = new MessageLogger();
board = new SituationBoard();
eventq = ListenerEventQueue.getEventQueue();
eventq.start();
eventq.addListener(logger, "Message");
eventq.addListener(logger, "Alert");
eventq.addListener(board, "Alert");
entry = new MessageEntrySystem();
entry.start();
}
}
/**
* An abstract element in a document
*
* This is used in an example of event bubbling
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public abstract class AbstractDocumentElement
extends AbstractEventHandler
{
/**
* Handle an event
*
* @param event The event to handle
* @return true if the event was handled
*/
public boolean handleEvent(BubblingEvent event)
{
boolean handled;
String name, value;
name = event.getName();
value = event.getValue();
handled = false;
if (name.equals("FaceChange"))
{
handled = handleFaceChange(value);
}
else if (name.equals("SizeChange"))
{
handled = handleSizeChange(value);
}
else if (name.equals("StyleChange"))
{
handled = handleStyleChange(value);
}
return handled;
}
/**
* Handle a font face change event
*
* @param face The new font face (e.g., TimesRoman)
*/
protected boolean handleFaceChange(String face)
{
return false;
}
/**
* Handle a font size change event
*
* @param pointSize The new font size (e.g., 12)
*/
protected boolean handleSizeChange(String pointSize)
{
return false;
}
/**
* Handle a font style change event
*
* @param style The new font style (e.g., Italic)
*/
protected boolean handleStyleChange(String style)
{
return false;
}
}
import java.io.*;
/**
* An encapsulation of a word in a document
*
* This is used in an example of event bubbling
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class Word extends AbstractDocumentElement
{
private Paragraph parent;
private String style, text;
/**
* Explicit Value Constructor
*
* @param text The text of the word
*/
public Word(String text)
{
super();
this.text = text;
style = null;
parent = null;
}
/**
* Get the parent of this EventHandler
*
* @return The parent
*/
public EventHandler getParent()
{
return parent;
}
/**
* Handle a font style change event
*
* @param style The new font style (e.g., Italic)
*/
protected boolean handleStyleChange(String style)
{
this.style = style;
return true;
}
/**
* Set the parent of this Word
*
* Note: In this example, a Word must be in a Paragraph.
* This could be relaxed.
*/
public void setParent(Paragraph p)
{
parent = p;
}
/**
* Print this Word
*
* @param out The PrintStream to use
*/
public void print(PrintStream out)
{
out.println("[Style: "+style+"]\t"+ text + " ");
}
}
import java.io.*;
/**
* An encapsulation of a paragraph in a document
*
* This is used in an example of event bubbling
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class Paragraph extends AbstractDocumentElement
{
private Section parent;
private String pointSize, style;
/**
* Default Constructor
*/
public Paragraph()
{
super();
pointSize = null;
style = null;
parent = null;
}
/**
* Get the parent of this EventHandler
*
* @return The parent
*/
public EventHandler getParent()
{
return parent;
}
/**
* Handle a font size change event
*
* @param pointSize The new font size (e.g., 12)
*/
protected boolean handleSizeChange(String pointSize)
{
this.pointSize = pointSize;
return true;
}
/**
* Handle a font style change event
*
* @param style The new font style (e.g., Italic)
*/
protected boolean handleStyleChange(String style)
{
this.style = style;
return true;
}
/**
* Print this Paragraph
*
* @param out The PrintStream to use
*/
public void print(PrintStream out)
{
out.println("Size: "+pointSize+"\tStyle: "+style);
}
/**
* Set the parent of this Paragraph
*
* Note: In this example, a Paragraph must be in a Section.
* This could be relaxed.
*/
public void setParent(Section s)
{
parent = s;
}
}
import java.io.*;
/**
* An encapsulation of a section of in a document
*
* This is used in an example of event bubbling
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class Section extends AbstractDocumentElement
{
private String face, pointSize, style;
/**
* Default Constructor
*/
public Section()
{
super();
face = null;
pointSize = null;
style = null;
}
/**
* Get the parent of this EventHandler
*
* @return The parent
*/
public EventHandler getParent()
{
return null;
}
/**
* Handle a font face change event
*
* Note: These events are handled by this Section
*
* @param face The new font face (e.g., TimesRoman)
*/
protected boolean handleFaceChange(String face)
{
this.face = face;
return true;
}
/**
* Handle a font size change event
*
* @param pointSize The new font size (e.g., 12)
*/
protected boolean handleSizeChange(String pointSize)
{
this.pointSize = pointSize;
return true;
}
/**
* Handle a font style change event
*
* @param style The new font style (e.g., Italic)
*/
protected boolean handleStyleChange(String style)
{
this.style = style;
return true;
}
/**
* Print this Section
*
* @param out The PrintStream to use
*/
public void print(PrintStream out)
{
out.println("Face: "+face+"\t"+"Size: "+pointSize+"\tStyle: "+style);
}
}
/**
* An example of event bubbling
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class BubblingDriver
{
private static Paragraph[] paragraphs;
private static Section section;
private static Word[] words;
/**
* The entry point of the application
*
* @param args The command-line arguments
*/
public static void main(String[] args)
{
BubblingEventQueue eventq;
BubblingEvent event;
words = new Word[8];
words[0] = new Word("This");
words[1] = new Word("is");
words[2] = new Word("paragraph");
words[3] = new Word("one");
words[4] = new Word("This");
words[5] = new Word("is");
words[6] = new Word("paragraph");
words[7] = new Word("two");
paragraphs = new Paragraph[2];
paragraphs[0] = new Paragraph();
for (int i=0; i<4; i++) words[i].setParent(paragraphs[0]);
paragraphs[1] = new Paragraph();
for (int i=4; i<8; i++) words[i].setParent(paragraphs[1]);
section = new Section();
paragraphs[0].setParent(section);
paragraphs[1].setParent(section);
eventq = BubblingEventQueue.getEventQueue();
eventq.start();
System.out.println("Initial Document");
print();
// Change the face for the Section (can't bubble)
event = new BubblingEvent(section, "FaceChange", "Futura");
eventq.postEvent(event);
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
System.out.println("\n\nAfter Section face change");
print();
// Change the style for a Word (shouldn't bubble)
event = new BubblingEvent(words[4], "StyleChange", "Italic");
eventq.postEvent(event);
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
System.out.println("\n\nAfter Word style change");
print();
// Change the size for a Paragraph (shouldn't bubble)
event = new BubblingEvent(paragraphs[0], "SizeChange", "12");
eventq.postEvent(event);
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
System.out.println("\n\nAfter Paragraph size change");
print();
// Change the face for a Word (should bubble to Section
event = new BubblingEvent(words[0], "FaceChange", "Garamond");
eventq.postEvent(event);
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
System.out.println("\n\nAfter Word face change");
print();
}
/**
* Prnt the current state
*/
private static void print()
{
section.print(System.out);
paragraphs[0].print(System.out);
for (int i=0; i<4; i++) words[i].print(System.out);
paragraphs[1].print(System.out);
for (int i=4; i<8; i++) words[i].print(System.out);
}
}