blob: 2dc7188c7d8095bb696a452dbdc743cc172dd338 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.framework.eventmgr;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
/**
* This class is the central class for the Event Manager. Each
* program that wishes to use the Event Manager should construct
* an EventManager object and use that object to construct
* ListenerQueue for dispatching events. CopyOnWriteIdentityMap objects
* must be used to manage listener lists.
*
* <p>This example uses the fictitious SomeEvent class and shows how to use this package
* to deliver a SomeEvent to a set of SomeEventListeners.
* <pre>
*
* // Create an EventManager with a name for an asynchronous event dispatch thread
* EventManager eventManager = new EventManager("SomeEvent Async Event Dispatcher Thread");
* // Create a CopyOnWriteIdentityMap to hold the list of SomeEventListeners
* Map eventListeners = new CopyOnWriteIdentityMap();
*
* // Add a SomeEventListener to the listener list
* eventListeners.put(someEventListener, null);
*
* // Asynchronously deliver a SomeEvent to registered SomeEventListeners
* // Create the listener queue for this event delivery
* ListenerQueue listenerQueue = new ListenerQueue(eventManager);
* // Add the listeners to the queue and associate them with the event dispatcher
* listenerQueue.queueListeners(eventListeners.entrySet(), new EventDispatcher() {
* public void dispatchEvent(Object eventListener, Object listenerObject,
* int eventAction, Object eventObject) {
* try {
* (SomeEventListener)eventListener.someEventOccured((SomeEvent)eventObject);
* } catch (Throwable t) {
* // properly log/handle any Throwable thrown by the listener
* }
* }
* });
* // Deliver the event to the listeners.
* listenerQueue.dispatchEventAsynchronous(0, new SomeEvent());
*
* // Remove the listener from the listener list
* eventListeners.remove(someEventListener);
*
* // Close EventManager to clean when done to terminate async event dispatch thread.
* // Note that closing the event manager while asynchronously delivering events
* // may cause some events to not be delivered before the async event dispatch
* // thread terminates
* eventManager.close();
* </pre>
*
* <p>At first glance, this package may seem more complicated than necessary
* but it has support for some important features. The listener list supports
* companion objects for each listener object. This is used by the OSGi framework
* to create wrapper objects for a listener which are passed to the event dispatcher.
* The ListenerQueue class is used to build a snap shot of the listeners prior to beginning
* event dispatch.
*
* The OSGi framework uses a 2 level listener list for each listener type (4 types).
* Level one is managed per framework instance and contains the list of BundleContexts which have
* registered a listener. Level 2 is managed per BundleContext for the listeners in that
* context. This allows all the listeners of a bundle to be easily and atomically removed from
* the level one list. To use a "flat" list for all bundles would require the list to know which
* bundle registered a listener object so that the list could be traversed when stopping a bundle
* to remove all the bundle's listeners.
*
* When an event is fired, a snapshot list (ListenerQueue) must be made of the current listeners before delivery
* is attempted. The snapshot list is necessary to allow the listener list to be modified while the
* event is being delivered to the snapshot list. The memory cost of the snapshot list is
* low since the ListenerQueue object uses the copy-on-write semantics
* of the CopyOnWriteIdentityMap. This guarantees the snapshot list is never modified once created.
*
* The OSGi framework also uses a 2 level dispatch technique (EventDispatcher).
* Level one dispatch is used by the framework to add the level 2 listener list of each
* BundleContext to the snapshot in preparation for delivery of the event.
* Level 2 dispatch is used as the final event deliverer and must cast the listener
* and event objects to the proper type before calling the listener. Level 2 dispatch
* will cancel delivery of an event
* to a bundle that has stopped between the time the snapshot was created and the
* attempt was made to deliver the event.
*
* <p> The highly dynamic nature of the OSGi framework had necessitated these features for
* proper and efficient event delivery.
* @since 3.1
* @noextend This class is not intended to be subclassed by clients.
*/
public class EventManager {
static final boolean DEBUG = false;
/**
* EventThread for asynchronous dispatch of events.
* Access to this field must be protected by a synchronized region.
*/
private EventThread thread;
/**
* Once closed, an attempt to create a new EventThread will result in an
* IllegalStateException.
*/
private boolean closed;
/**
* Thread name used for asynchronous event delivery
*/
protected final String threadName;
/**
* The thread group used for asynchronous event delivery
*/
protected final ThreadGroup threadGroup;
/**
* EventManager constructor. An EventManager object is responsible for
* the delivery of events to listeners via an EventDispatcher.
*
*/
public EventManager() {
this(null, null);
}
/**
* EventManager constructor. An EventManager object is responsible for
* the delivery of events to listeners via an EventDispatcher.
*
* @param threadName The name to give the event thread associated with
* this EventManager. A <code>null</code> value is allowed.
*/
public EventManager(String threadName) {
this(threadName, null);
}
/**
* EventManager constructor. An EventManager object is responsible for
* the delivery of events to listeners via an EventDispatcher.
*
* @param threadName The name to give the event thread associated with
* this EventManager. A <code>null</code> value is allowed.
* @param threadGroup The thread group to use for the asynchronous event
* thread associated with this EventManager. A <code>null</code> value is allowed.
* @since 3.4
*/
public EventManager(String threadName, ThreadGroup threadGroup) {
thread = null;
closed = false;
this.threadName = threadName;
this.threadGroup = threadGroup;
}
/**
* This method can be called to release any resources associated with this
* EventManager.
* <p>
* Closing this EventManager while it is asynchronously delivering events
* may cause some events to not be delivered before the async event dispatch
* thread terminates.
*/
public synchronized void close() {
if (closed) {
return;
}
if (thread != null) {
thread.close();
thread = null;
}
closed = true;
}
/**
* Returns the EventThread to use for dispatching events asynchronously for
* this EventManager.
*
* @return EventThread to use for dispatching events asynchronously for
* this EventManager.
*/
synchronized EventThread getEventThread() {
if (closed) {
throw new IllegalStateException();
}
if (thread == null) {
/* if there is no thread, then create a new one */
thread = (EventThread) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
EventThread t = new EventThread(threadGroup, threadName);
return t;
}
});
/* start the new thread */
thread.start();
}
return thread;
}
/**
* This method calls the EventDispatcher object to complete the dispatch of
* the event. If there are more elements in the list, call dispatchEvent
* on the next item on the list.
* This method is package private.
*
* @param listeners A Set of entries from a CopyOnWriteIdentityMap map.
* @param dispatcher Call back object which is called to complete the delivery of
* the event.
* @param eventAction This value was passed by the event source and
* is passed to this method. This is passed on to the call back object.
* @param eventObject This object was created by the event source and
* is passed to this method. This is passed on to the call back object.
*/
static void dispatchEvent(Set/*<Map.Entry<Object,Object>>*/listeners, EventDispatcher dispatcher, int eventAction, Object eventObject) {
for (Iterator iter = listeners.iterator(); iter.hasNext();) { /* iterate over the list of listeners */
Map.Entry listener = (Map.Entry) iter.next();
Object eventListener = listener.getKey();
Object listenerObject = listener.getValue();
try {
/* Call the EventDispatcher to complete the delivery of the event. */
dispatcher.dispatchEvent(eventListener, listenerObject, eventAction, eventObject);
} catch (Throwable t) {
/* Consume and ignore any exceptions thrown by the listener */
if (DEBUG) {
System.out.println("Exception in " + listener.getKey()); //$NON-NLS-1$
t.printStackTrace();
}
}
}
}
/**
* This package private class is used for asynchronously dispatching events.
*/
static class EventThread extends Thread {
private static int nextThreadNumber;
/**
* Queued is a nested top-level (non-member) class. This class
* represents the items which are placed on the asynch dispatch queue.
* This class is private.
*/
private static class Queued {
/** listener list for this event */
final Set/*<Map.Entry<Object,Object>>*/listeners;
/** dispatcher of this event */
final EventDispatcher dispatcher;
/** action for this event */
final int action;
/** object for this event */
final Object object;
/** next item in event queue */
Queued next;
/**
* Constructor for event queue item
*
* @param l Listener list for this event
* @param d Dispatcher for this event
* @param a Action for this event
* @param o Object for this event
*/
Queued(Set/*<Map.Entry<Object,Object>>*/l, EventDispatcher d, int a, Object o) {
listeners = l;
dispatcher = d;
action = a;
object = o;
next = null;
}
}
/** item at the head of the event queue */
private Queued head;
/** item at the tail of the event queue */
private Queued tail;
/** if false the thread must terminate */
private volatile boolean running;
/**
* Constructor for the event thread.
* @param threadName Name of the EventThread
*/
EventThread(ThreadGroup threadGroup, String threadName) {
super(threadGroup, threadName == null ? getNextName() : threadName);
running = true;
head = null;
tail = null;
setDaemon(true); /* Mark thread as daemon thread */
}
private static synchronized String getNextName() {
return "EventManagerThread-" + nextThreadNumber++; //$NON-NLS-1$
}
/**
* Constructor for the event thread.
* @param threadName Name of the EventThread
*/
EventThread(String threadName) {
this(null, threadName);
}
/**
* Constructor for the event thread.
*/
EventThread() {
this(null, null);
}
/**
* Stop thread.
*/
void close() {
running = false;
interrupt();
}
/**
* This method pulls events from
* the queue and dispatches them.
*/
public void run() {
try {
while (true) {
Queued item = getNextEvent();
if (item == null) {
return;
}
EventManager.dispatchEvent(item.listeners, item.dispatcher, item.action, item.object);
// Bug 299589: since the call to getNextEvent() will eventually block for a long time, we need to make sure that the 'item'
// variable is cleared of the previous value before the call to getNextEvent(). See VM SPec 2.5.7 for why the compiler
// will not automatically clear this variable for each loop iteration.
item = null;
}
} catch (RuntimeException e) {
if (EventManager.DEBUG) {
e.printStackTrace();
}
throw e;
} catch (Error e) {
if (EventManager.DEBUG) {
e.printStackTrace();
}
throw e;
}
}
/**
* This methods takes the input parameters and creates a Queued
* object and queues it.
* The thread is notified.
*
* @param l Listener list for this event
* @param d Dispatcher for this event
* @param a Action for this event
* @param o Object for this event
*/
synchronized void postEvent(Set/*<Map.Entry<Object,Object>>*/l, EventDispatcher d, int a, Object o) {
if (!isAlive()) { /* If the thread is not alive, throw an exception */
throw new IllegalStateException();
}
Queued item = new Queued(l, d, a, o);
if (head == null) /* if the queue was empty */
{
head = item;
tail = item;
} else /* else add to end of queue */
{
tail.next = item;
tail = item;
}
notify();
}
/**
* This method is called by the thread to remove
* items from the queue so that they can be dispatched to their listeners.
* If the queue is empty, the thread waits.
*
* @return The Queued removed from the top of the queue or null
* if the thread has been requested to stop.
*/
private synchronized Queued getNextEvent() {
while (running && (head == null)) {
try {
wait();
} catch (InterruptedException e) {
// If interrupted, we will loop back up and check running
}
}
if (!running) { /* if we are stopping */
return null;
}
Queued item = head;
head = item.next;
if (head == null) {
tail = null;
}
return item;
}
}
}