/*******************************************************************************
 * Copyright (c) 2012 Tasktop Technologies 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:
 *     Tasktop Technologies - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.commons.core;

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;

/**
 * A list like class for managing listeners. It is safe to call this class from different threads concurrently.
 * 
 * @since 3.7
 */
public class CommonListenerList<T> implements Iterable<T> {

	/**
	 * Subclasses should extend to not
	 */
	public static abstract class Notifier<T> {

		/**
		 * Fires an event to <code>listener</code>.
		 * 
		 * @param listener
		 *            the listener to be notified
		 * @throws Exception
		 *             indicates a unrecoverable problem with <code>listener</code>
		 */
		public abstract void run(T listener) throws Exception;

	}

	private final CopyOnWriteArrayList<T> listeners;

	private final String pluginId;

	/**
	 * Constructs an empty list.
	 * 
	 * @param pluginId
	 *            the ID of the bundle that is managing this instance
	 */
	public CommonListenerList(String pluginId) {
		Assert.isNotNull(pluginId);
		this.pluginId = pluginId;
		this.listeners = new CopyOnWriteArrayList<T>();
	}

	/**
	 * Adds <code>listener</code> to the list of listeners.
	 */
	public void add(T listener) {
		Assert.isNotNull(listener);
		listeners.addIfAbsent(listener);
	}

	/**
	 * Iterates over the list of listeners.
	 */
	public Iterator<T> iterator() {
		return listeners.iterator();
	}

	/**
	 * Invokes <code>runnable</code> for each listener. If {@link Notifier#run(Object)} throws an exception the
	 * corresponding listener is removed from the list and a message is logged.
	 */
	public void notify(final Notifier<T> runnable) {
		for (final T listener : listeners) {
			SafeRunner.run(new ISafeRunnable() {
				public void handleException(Throwable e) {
					StatusHandler.log(new Status(IStatus.ERROR, pluginId, NLS.bind(
							"Unexpected error notifying listener {0}", listener.getClass()), e)); //$NON-NLS-1$
					remove(listener);
				}

				public void run() throws Exception {
					runnable.run(listener);
				}
			});
		}
	}

	/**
	 * Removes <code>listener</code> to the list of listeners.
	 */
	public void remove(T listener) {
		listeners.remove(listener);
	}

}
