Mercurial > projects > dwt2
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 } |