/*******************************************************************************
 * Copyright (c) 2005, 2006 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.core.databinding.observable;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.eclipse.core.internal.databinding.IdentityWrapper;
import org.eclipse.core.runtime.Assert;

/**
 * This class makes it possible to monitor whenever an IObservable is read from.
 * This can be used to automatically attach and remove listeners. How to use it:
 * 
 * <p>
 * If you are implementing an IObservable, invoke getterCalled(this) whenever a
 * getter is called - that is, whenever your observable is read from. You only
 * need to do this once per method call. If one getter delegates to another, the
 * outer getter doesn't need to call the method since the inner one will.
 * </p>
 * 
 * <p>
 * If you want to determine what observables were used in a particular block of
 * code, call runAndMonitor(Runnable). This will execute the given runnable and
 * return the set of observables that were read from.
 * </p>
 * 
 * <p>
 * This can be used to automatically attach listeners. For example, imagine you
 * have a block of code that updates some widget by reading from a bunch of
 * observables. Whenever one of those observables changes, you want to re-run
 * the code and cause the widget to be refreshed. You could do this in the
 * traditional manner by attaching one listener to each observable and
 * re-running your widget update code whenever one of them changes, but this
 * code is repetitive and requires updating the listener code whenever you
 * refactor the widget updating code.
 * </p>
 * 
 * <p>
 * Alternatively, you could use a utility class that runs the code in a
 * runAndMonitor block and automatically attach listeners to any observable used
 * in updating the widget. The advantage of the latter approach is that it,
 * eliminates the code for attaching and detaching listeners and will always
 * stay in synch with changes to the widget update logic.
 * </p>
 * 
 * @since 1.0
 */
public class ObservableTracker {

	/**
	 * Threadlocal storage pointing to the current Set of IObservables, or null
	 * if none. Note that this is actually the top of a stack. Whenever a method
	 * changes the current value, it remembers the old value as a local variable
	 * and restores the old value when the method exits.
	 */
	private static ThreadLocal currentChangeListener = new ThreadLocal();

	private static ThreadLocal currentStaleListener = new ThreadLocal();

	private static ThreadLocal currentObservableSet = new ThreadLocal();

	/**
	 * Invokes the given runnable, and returns the set of IObservables that were
	 * read by the runnable. If the runnable calls this method recursively, the
	 * result will not contain IObservables that were used within the inner
	 * runnable.
	 * 
	 * @param runnable
	 *            runnable to execute
	 * @param changeListener
	 *            listener to register with all accessed observables
	 * @param staleListener
	 *            listener to register with all accessed observables, or
	 *            <code>null</code> if no stale listener is to be registered
	 * @return an array of unique observable objects
	 */
	public static IObservable[] runAndMonitor(Runnable runnable,
			IChangeListener changeListener, IStaleListener staleListener) {
		// Remember the previous value in the listener stack
		Set lastObservableSet = (Set) currentObservableSet.get();
		IChangeListener lastChangeListener = (IChangeListener) currentChangeListener
				.get();
		IStaleListener lastStaleListener = (IStaleListener) currentStaleListener
				.get();

		Set observableSet = new HashSet();
		// Push the new listeners to the top of the stack
		currentObservableSet.set(observableSet);
		currentChangeListener.set(changeListener);
		currentStaleListener.set(staleListener);
		try {
			runnable.run();
		} finally {
			// Pop the new listener off the top of the stack (by restoring the
			// previous listener)
			currentObservableSet.set(lastObservableSet);
			currentChangeListener.set(lastChangeListener);
			currentStaleListener.set(lastStaleListener);
		}

		int i = 0;
		IObservable[] result = new IObservable[observableSet.size()];
		for (Iterator it = observableSet.iterator(); it.hasNext();) {
			IdentityWrapper wrapper = (IdentityWrapper) it.next();
			result[i++] = (IObservable) wrapper.unwrap();
		}

		return result;
	}

	/**
	 * Notifies the ObservableTracker that an observable was read from. The
	 * JavaDoc for methods that invoke this method should include the following
	 * tag: "@TrackedGetter This method will notify ObservableTracker that the
	 * receiver has been read from". This lets callers know that they can rely
	 * on automatic updates from the object without explicitly attaching a
	 * listener.
	 * 
	 * @param observable
	 */
	public static void getterCalled(IObservable observable) {
		Assert.isTrue(observable.getRealm().isCurrent());
		Set lastObservableSet = (Set) currentObservableSet.get();
		IChangeListener lastChangeListener = (IChangeListener) currentChangeListener
				.get();
		IStaleListener lastStaleListener = (IStaleListener) currentStaleListener
				.get();

		boolean added = false;
		if (lastObservableSet != null) {
			added = lastObservableSet.add(new IdentityWrapper(observable));
		}

		// If anyone is listening for observable usage...
		if (added && lastChangeListener != null) {
			observable.addChangeListener(lastChangeListener);
		}
		if (added && lastStaleListener != null) {
			observable.addStaleListener(lastStaleListener);
		}
	}
}
