/*******************************************************************************
 * Copyright (c) 2003, 2016 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.osgi.framework.eventmgr;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Map;
import java.util.Set;

/**
 * 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 <K, V, E> EventThread<K, V, E> getEventThread() {
		if (closed) {
			throw new IllegalStateException();
		}
		if (thread == null) {
			/* if there is no thread, then create a new one */
			thread = AccessController.doPrivileged(new PrivilegedAction<EventThread<K, V, E>>() {
				@Override
				public EventThread<K, V, E> run() {
					EventThread<K, V, E> t = new EventThread<>(threadGroup, threadName);
					return t;
				}
			});
			/* start the new thread */
			thread.start();
		}

		@SuppressWarnings("unchecked")
		EventThread<K, V, E> result = (EventThread<K, V, E>) thread;
		return result;
	}

	/**
	 * 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 <K, V, E> void dispatchEvent(Set<Map.Entry<K, V>> listeners, EventDispatcher<K, V, E> dispatcher, int eventAction, E eventObject) {
		for (Map.Entry<K, V> listener : listeners) { /* iterate over the list of listeners */
			final K eventListener = listener.getKey();
			final V 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 " + eventListener); //$NON-NLS-1$
					t.printStackTrace();
				}
			}
		}
	}

	/**
	 * This package private class is used for asynchronously dispatching events.
	 */

	static class EventThread<K, V, E> 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<K, V, E> {
			/** listener list for this event */
			final Set<Map.Entry<K, V>> listeners;
			/** dispatcher of this event */
			final EventDispatcher<K, V, E> dispatcher;
			/** action for this event */
			final int action;
			/** object for this event */
			final E object;
			/** next item in event queue */
			Queued<K, V, E> 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<K, V>> l, EventDispatcher<K, V, E> d, int a, E o) {
				listeners = l;
				dispatcher = d;
				action = a;
				object = o;
				next = null;
			}
		}

		/** item at the head of the event queue */
		private Queued<K, V, E> head;
		/** item at the tail of the event queue */
		private Queued<K, V, E> 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.
		 */
		@Override
		public void run() {
			try {
				while (true) {
					Queued<K, V, E> 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 | 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<K, V>> l, EventDispatcher<K, V, E> d, int a, E o) {
			if (!isAlive()) { /* If the thread is not alive, throw an exception */
				throw new IllegalStateException();
			}

			Queued<K, V, E> 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<K, V, E> 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<K, V, E> item = head;
			head = item.next;
			if (head == null) {
				tail = null;
			}

			return item;
		}
	}
}
