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