diff org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ObservableTracker.d @ 95:6208d4f6a277

Added trees for databinding.beans and observable
author Frank Benoit <benoit@tionex.de>
date Tue, 21 Apr 2009 10:55:51 +0200
parents
children b74ac5dfcc06
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ObservableTracker.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,200 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 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
+ *     Matthew Hall - Fix NPE, more detailed assert messages (bug 210115)
+ *******************************************************************************/
+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 runAndMonitorcast(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 = cast(Set) currentObservableSet.get();
+        IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener
+                .get();
+        IStaleListener lastStaleListener = cast(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 = cast(IdentityWrapper) it.next();
+            result[i++] = cast(IObservable) wrapper.unwrap();
+        }
+
+        return result;
+    }
+    
+    /**
+     * Runs the given runnable without tracking dependencies.
+     * @param runnable
+     * 
+     * @since 1.1
+     */
+    public static void runAndIgnore(Runnable runnable) {
+        // Remember the previous value in the listener stack
+        Set lastObservableSet = cast(Set) currentObservableSet.get();
+        IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener
+                .get();
+        IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener
+                .get();
+        currentObservableSet.set(null);
+        currentChangeListener.set(null);
+        currentStaleListener.set(null);
+        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);
+        }
+    }
+
+    /*
+     * Returns the same string as the default Object.toString() implementation.
+     * getterCalled() uses this method IObservable.toString() to avoid infinite
+     * recursion and stack overflow.
+     */
+    private static String toString(IObservable observable) {
+        return observable.getClass().getName() + "@" //$NON-NLS-1$
+                + Integer.toHexString(System.identityHashCode(observable));
+    }
+
+    /**
+     * 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) {
+        Realm realm = observable.getRealm();
+        if (realm is null) // observable.isDisposed() would be more appropriate if it existed
+            Assert.isTrue(false, "Getter called on disposed observable " //$NON-NLS-1$
+                    + toString(observable));
+        if (!realm.isCurrent())
+            Assert.isTrue(false, "Getter called outside realm of observable " //$NON-NLS-1$
+                    + toString(observable));
+
+        Set lastObservableSet = cast(Set) currentObservableSet.get();
+        if (lastObservableSet is null) {
+            return;
+        }
+        IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener
+                .get();
+        IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener
+                .get();
+
+        bool added = false;
+        if (lastObservableSet !is null) {
+            added = lastObservableSet.add(new IdentityWrapper(observable));
+        }
+
+        // If anyone is listening for observable usage...
+        if (added && lastChangeListener !is null) {
+            observable.addChangeListener(lastChangeListener);
+        }
+        if (added && lastStaleListener !is null) {
+            observable.addStaleListener(lastStaleListener);
+        }
+    }
+}