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 */
|
|
73 private static ThreadLocal currentChangeListener = new ThreadLocal();
|
|
74
|
|
75 private static ThreadLocal currentStaleListener = new ThreadLocal();
|
|
76
|
|
77 private static ThreadLocal currentObservableSet = new ThreadLocal();
|
|
78
|
|
79 /**
|
|
80 * Invokes the given runnable, and returns the set of IObservables that were
|
|
81 * read by the runnable. If the runnable calls this method recursively, the
|
|
82 * result will not contain IObservables that were used within the inner
|
|
83 * runnable.
|
|
84 *
|
|
85 * @param runnable
|
|
86 * runnable to execute
|
|
87 * @param changeListener
|
|
88 * listener to register with all accessed observables
|
|
89 * @param staleListener
|
|
90 * listener to register with all accessed observables, or
|
|
91 * <code>null</code> if no stale listener is to be registered
|
|
92 * @return an array of unique observable objects
|
|
93 */
|
|
94 public static IObservable[] runAndMonitor(Runnable runnable,
|
|
95 IChangeListener changeListener, IStaleListener staleListener) {
|
|
96 // Remember the previous value in the listener stack
|
|
97 Set lastObservableSet = cast(Set) currentObservableSet.get();
|
|
98 IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener
|
|
99 .get();
|
|
100 IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener
|
|
101 .get();
|
|
102
|
|
103 Set observableSet = new HashSet();
|
|
104 // Push the new listeners to the top of the stack
|
|
105 currentObservableSet.set(observableSet);
|
|
106 currentChangeListener.set(changeListener);
|
|
107 currentStaleListener.set(staleListener);
|
|
108 try {
|
|
109 runnable.run();
|
|
110 } finally {
|
|
111 // Pop the new listener off the top of the stack (by restoring the
|
|
112 // previous listener)
|
|
113 currentObservableSet.set(lastObservableSet);
|
|
114 currentChangeListener.set(lastChangeListener);
|
|
115 currentStaleListener.set(lastStaleListener);
|
|
116 }
|
|
117
|
|
118 int i = 0;
|
|
119 IObservable[] result = new IObservable[observableSet.size()];
|
|
120 for (Iterator it = observableSet.iterator(); it.hasNext();) {
|
|
121 IdentityWrapper wrapper = cast(IdentityWrapper) it.next();
|
|
122 result[i++] = cast(IObservable) wrapper.unwrap();
|
|
123 }
|
|
124
|
|
125 return result;
|
|
126 }
|
|
127
|
|
128 /**
|
|
129 * Runs the given runnable without tracking dependencies.
|
|
130 * @param runnable
|
|
131 *
|
|
132 * @since 1.1
|
|
133 */
|
|
134 public static void runAndIgnore(Runnable runnable) {
|
|
135 // Remember the previous value in the listener stack
|
|
136 Set lastObservableSet = cast(Set) currentObservableSet.get();
|
|
137 IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener
|
|
138 .get();
|
|
139 IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener
|
|
140 .get();
|
|
141 currentObservableSet.set(null);
|
|
142 currentChangeListener.set(null);
|
|
143 currentStaleListener.set(null);
|
|
144 try {
|
|
145 runnable.run();
|
|
146 } finally {
|
|
147 // Pop the new listener off the top of the stack (by restoring the
|
|
148 // previous listener)
|
|
149 currentObservableSet.set(lastObservableSet);
|
|
150 currentChangeListener.set(lastChangeListener);
|
|
151 currentStaleListener.set(lastStaleListener);
|
|
152 }
|
|
153 }
|
|
154
|
|
155 /*
|
|
156 * Returns the same string as the default Object.toString() implementation.
|
|
157 * getterCalled() uses this method IObservable.toString() to avoid infinite
|
|
158 * recursion and stack overflow.
|
|
159 */
|
|
160 private static String toString(IObservable observable) {
|
|
161 return observable.getClass().getName() + "@" //$NON-NLS-1$
|
|
162 + Integer.toHexString(System.identityHashCode(observable));
|
|
163 }
|
|
164
|
|
165 /**
|
|
166 * Notifies the ObservableTracker that an observable was read from. The
|
|
167 * JavaDoc for methods that invoke this method should include the following
|
|
168 * tag: "@TrackedGetter This method will notify ObservableTracker that the
|
|
169 * receiver has been read from". This lets callers know that they can rely
|
|
170 * on automatic updates from the object without explicitly attaching a
|
|
171 * listener.
|
|
172 *
|
|
173 * @param observable
|
|
174 */
|
|
175 public static void getterCalled(IObservable observable) {
|
|
176 Realm realm = observable.getRealm();
|
|
177 if (realm is null) // observable.isDisposed() would be more appropriate if it existed
|
|
178 Assert.isTrue(false, "Getter called on disposed observable " //$NON-NLS-1$
|
|
179 + toString(observable));
|
|
180 if (!realm.isCurrent())
|
|
181 Assert.isTrue(false, "Getter called outside realm of observable " //$NON-NLS-1$
|
|
182 + toString(observable));
|
|
183
|
|
184 Set lastObservableSet = cast(Set) currentObservableSet.get();
|
|
185 if (lastObservableSet is null) {
|
|
186 return;
|
|
187 }
|
|
188 IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener
|
|
189 .get();
|
|
190 IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener
|
|
191 .get();
|
|
192
|
|
193 bool added = false;
|
|
194 if (lastObservableSet !is null) {
|
|
195 added = lastObservableSet.add(new IdentityWrapper(observable));
|
|
196 }
|
|
197
|
|
198 // If anyone is listening for observable usage...
|
|
199 if (added && lastChangeListener !is null) {
|
|
200 observable.addChangeListener(lastChangeListener);
|
|
201 }
|
|
202 if (added && lastStaleListener !is null) {
|
|
203 observable.addStaleListener(lastStaleListener);
|
|
204 }
|
|
205 }
|
|
206 }
|