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); } }