comparison org.eclipse.core.databinding/src/org/eclipse/core/databinding/observable/ObservableTracker.d @ 78:0a55d2d5a946

Added file for databinding
author Frank Benoit <benoit@tionex.de>
date Tue, 14 Apr 2009 11:35:29 +0200
parents
children 383ce7bd736b
comparison
equal deleted inserted replaced
76:f05e6e8b2f2d 78:0a55d2d5a946
1 /*******************************************************************************
2 * Copyright (c) 2005, 2008 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 * Matthew Hall - Fix NPE, more detailed assert messages (bug 210115)
11 *******************************************************************************/
12 module org.eclipse.core.databinding.observable.ObservableTracker;
13
14 import java.lang.all;
15
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.Set;
19
20 import org.eclipse.core.internal.databinding.IdentityWrapper;
21 import org.eclipse.core.runtime.Assert;
22
23 /**
24 * This class makes it possible to monitor whenever an IObservable is read from.
25 * This can be used to automatically attach and remove listeners. How to use it:
26 *
27 * <p>
28 * If you are implementing an IObservable, invoke getterCalled(this) whenever a
29 * getter is called - that is, whenever your observable is read from. You only
30 * need to do this once per method call. If one getter delegates to another, the
31 * outer getter doesn't need to call the method since the inner one will.
32 * </p>
33 *
34 * <p>
35 * If you want to determine what observables were used in a particular block of
36 * code, call runAndMonitorcast(Runnable). This will execute the given runnable and
37 * return the set of observables that were read from.
38 * </p>
39 *
40 * <p>
41 * This can be used to automatically attach listeners. For example, imagine you
42 * have a block of code that updates some widget by reading from a bunch of
43 * observables. Whenever one of those observables changes, you want to re-run
44 * the code and cause the widget to be refreshed. You could do this in the
45 * traditional manner by attaching one listener to each observable and
46 * re-running your widget update code whenever one of them changes, but this
47 * code is repetitive and requires updating the listener code whenever you
48 * refactor the widget updating code.
49 * </p>
50 *
51 * <p>
52 * Alternatively, you could use a utility class that runs the code in a
53 * runAndMonitor block and automatically attach listeners to any observable used
54 * in updating the widget. The advantage of the latter approach is that it,
55 * eliminates the code for attaching and detaching listeners and will always
56 * stay in synch with changes to the widget update logic.
57 * </p>
58 *
59 * @since 1.0
60 */
61 public class ObservableTracker {
62
63 /**
64 * Threadlocal storage pointing to the current Set of IObservables, or null
65 * if none. Note that this is actually the top of a stack. Whenever a method
66 * changes the current value, it remembers the old value as a local variable
67 * and restores the old value when the method exits.
68 */
69 private static ThreadLocal currentChangeListener = new ThreadLocal();
70
71 private static ThreadLocal currentStaleListener = new ThreadLocal();
72
73 private static ThreadLocal currentObservableSet = new ThreadLocal();
74
75 /**
76 * Invokes the given runnable, and returns the set of IObservables that were
77 * read by the runnable. If the runnable calls this method recursively, the
78 * result will not contain IObservables that were used within the inner
79 * runnable.
80 *
81 * @param runnable
82 * runnable to execute
83 * @param changeListener
84 * listener to register with all accessed observables
85 * @param staleListener
86 * listener to register with all accessed observables, or
87 * <code>null</code> if no stale listener is to be registered
88 * @return an array of unique observable objects
89 */
90 public static IObservable[] runAndMonitor(Runnable runnable,
91 IChangeListener changeListener, IStaleListener staleListener) {
92 // Remember the previous value in the listener stack
93 Set lastObservableSet = cast(Set) currentObservableSet.get();
94 IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener
95 .get();
96 IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener
97 .get();
98
99 Set observableSet = new HashSet();
100 // Push the new listeners to the top of the stack
101 currentObservableSet.set(observableSet);
102 currentChangeListener.set(changeListener);
103 currentStaleListener.set(staleListener);
104 try {
105 runnable.run();
106 } finally {
107 // Pop the new listener off the top of the stack (by restoring the
108 // previous listener)
109 currentObservableSet.set(lastObservableSet);
110 currentChangeListener.set(lastChangeListener);
111 currentStaleListener.set(lastStaleListener);
112 }
113
114 int i = 0;
115 IObservable[] result = new IObservable[observableSet.size()];
116 for (Iterator it = observableSet.iterator(); it.hasNext();) {
117 IdentityWrapper wrapper = cast(IdentityWrapper) it.next();
118 result[i++] = cast(IObservable) wrapper.unwrap();
119 }
120
121 return result;
122 }
123
124 /**
125 * Runs the given runnable without tracking dependencies.
126 * @param runnable
127 *
128 * @since 1.1
129 */
130 public static void runAndIgnore(Runnable runnable) {
131 // Remember the previous value in the listener stack
132 Set lastObservableSet = cast(Set) currentObservableSet.get();
133 IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener
134 .get();
135 IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener
136 .get();
137 currentObservableSet.set(null);
138 currentChangeListener.set(null);
139 currentStaleListener.set(null);
140 try {
141 runnable.run();
142 } finally {
143 // Pop the new listener off the top of the stack (by restoring the
144 // previous listener)
145 currentObservableSet.set(lastObservableSet);
146 currentChangeListener.set(lastChangeListener);
147 currentStaleListener.set(lastStaleListener);
148 }
149 }
150
151 /*
152 * Returns the same string as the default Object.toString() implementation.
153 * getterCalled() uses this method IObservable.toString() to avoid infinite
154 * recursion and stack overflow.
155 */
156 private static String toString(IObservable observable) {
157 return observable.getClass().getName() + "@" //$NON-NLS-1$
158 + Integer.toHexString(System.identityHashCode(observable));
159 }
160
161 /**
162 * Notifies the ObservableTracker that an observable was read from. The
163 * JavaDoc for methods that invoke this method should include the following
164 * tag: "@TrackedGetter This method will notify ObservableTracker that the
165 * receiver has been read from". This lets callers know that they can rely
166 * on automatic updates from the object without explicitly attaching a
167 * listener.
168 *
169 * @param observable
170 */
171 public static void getterCalled(IObservable observable) {
172 Realm realm = observable.getRealm();
173 if (realm is null) // observable.isDisposed() would be more appropriate if it existed
174 Assert.isTrue(false, "Getter called on disposed observable " //$NON-NLS-1$
175 + toString(observable));
176 if (!realm.isCurrent())
177 Assert.isTrue(false, "Getter called outside realm of observable " //$NON-NLS-1$
178 + toString(observable));
179
180 Set lastObservableSet = cast(Set) currentObservableSet.get();
181 if (lastObservableSet is null) {
182 return;
183 }
184 IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener
185 .get();
186 IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener
187 .get();
188
189 bool added = false;
190 if (lastObservableSet !is null) {
191 added = lastObservableSet.add(new IdentityWrapper(observable));
192 }
193
194 // If anyone is listening for observable usage...
195 if (added && lastChangeListener !is null) {
196 observable.addChangeListener(lastChangeListener);
197 }
198 if (added && lastStaleListener !is null) {
199 observable.addStaleListener(lastStaleListener);
200 }
201 }
202 }