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;
|
|
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 }
|