Mercurial > projects > dwt2
comparison org.eclipse.core.databinding/src/org/eclipse/core/databinding/observable/list/ComputedList.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) 2007 Matthew Hall and others. All rights reserved. This program and the | |
3 * accompanying materials are made available under the terms of the Eclipse Public License v1.0 which | |
4 * accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html | |
5 * | |
6 * Contributors: | |
7 * Matthew Hall - initial API and implementation | |
8 * IBM Corporation - initial API and implementation | |
9 * Brad Reynolds - initial API and implementation (through bug 116920 and bug 147515) | |
10 * Matthew Hall - bug 211786 | |
11 ***********************************************************************************************************/ | |
12 module org.eclipse.core.databinding.observable.list.ComputedList; | |
13 | |
14 import java.lang.all; | |
15 | |
16 import java.util.ArrayList; | |
17 import java.util.Collections; | |
18 import java.util.List; | |
19 | |
20 import org.eclipse.core.databinding.observable.ChangeEvent; | |
21 import org.eclipse.core.databinding.observable.Diffs; | |
22 import org.eclipse.core.databinding.observable.IChangeListener; | |
23 import org.eclipse.core.databinding.observable.IObservable; | |
24 import org.eclipse.core.databinding.observable.IStaleListener; | |
25 import org.eclipse.core.databinding.observable.ObservableTracker; | |
26 import org.eclipse.core.databinding.observable.Realm; | |
27 import org.eclipse.core.databinding.observable.StaleEvent; | |
28 | |
29 /** | |
30 * A Lazily calculated list that automatically computes and registers listeners | |
31 * on its dependencies as long as all of its dependencies are IObservable | |
32 * objects | |
33 * <p> | |
34 * This class is thread safe. All state accessing methods must be invoked from | |
35 * the {@link Realm#isCurrent() current realm}. Methods for adding and removing | |
36 * listeners may be invoked from any thread. | |
37 * </p> | |
38 * | |
39 * @since 1.1 | |
40 */ | |
41 public abstract class ComputedList : AbstractObservableList { | |
42 private List cachedList = new ArrayList(); | |
43 | |
44 private bool dirty = true; | |
45 private bool stale = false; | |
46 | |
47 private IObservable[] dependencies = new IObservable[0]; | |
48 | |
49 /** | |
50 * Creates a computed list in the default realm and with an unknown (null) | |
51 * element type. | |
52 */ | |
53 public this() { | |
54 this(Realm.getDefault(), null); | |
55 } | |
56 | |
57 /** | |
58 * Creates a computed list in the default realm and with the given element | |
59 * type. | |
60 * | |
61 * @param elementType | |
62 * the element type, may be <code>null</code> to indicate | |
63 * unknown element type | |
64 */ | |
65 public this(Object elementType) { | |
66 this(Realm.getDefault(), elementType); | |
67 } | |
68 | |
69 /** | |
70 * Creates a computed list in given realm and with an unknown (null) element | |
71 * type. | |
72 * | |
73 * @param realm | |
74 * the realm | |
75 * | |
76 */ | |
77 public this(Realm realm) { | |
78 this(realm, null); | |
79 } | |
80 | |
81 /** | |
82 * Creates a computed list in the given realm and with the given element | |
83 * type. | |
84 * | |
85 * @param realm | |
86 * the realm | |
87 * @param elementType | |
88 * the element type, may be <code>null</code> to indicate | |
89 * unknown element type | |
90 */ | |
91 public this(Realm realm, Object elementType) { | |
92 super(realm); | |
93 this.elementType = elementType; | |
94 } | |
95 | |
96 /** | |
97 * Inner class that implements interfaces that we don't want to expose as | |
98 * public API. Each interface could have been implemented using a separate | |
99 * anonymous class, but we combine them here to reduce the memory overhead | |
100 * and number of classes. | |
101 * | |
102 * <p> | |
103 * The Runnable calls calculate and stores the result in cachedList. | |
104 * </p> | |
105 * | |
106 * <p> | |
107 * The IChangeListener stores each observable in the dependencies list. This | |
108 * is registered as the listener when calling ObservableTracker, to detect | |
109 * every observable that is used by computeValue. | |
110 * </p> | |
111 * | |
112 * <p> | |
113 * The IChangeListener is attached to every dependency. | |
114 * </p> | |
115 * | |
116 */ | |
117 private class PrivateInterface : Runnable, IChangeListener, | |
118 IStaleListener { | |
119 public void run() { | |
120 cachedList = calculate(); | |
121 if (cachedList is null) | |
122 cachedList = Collections.EMPTY_LIST; | |
123 } | |
124 | |
125 public void handleStale(StaleEvent event) { | |
126 if (!dirty) | |
127 makeStale(); | |
128 } | |
129 | |
130 public void handleChange(ChangeEvent event) { | |
131 makeDirty(); | |
132 } | |
133 } | |
134 | |
135 private PrivateInterface privateInterface = new PrivateInterface(); | |
136 | |
137 private Object elementType; | |
138 | |
139 protected int doGetSize() { | |
140 return doGetList().size(); | |
141 } | |
142 | |
143 public Object get(int index) { | |
144 getterCalled(); | |
145 return doGetList().get(index); | |
146 } | |
147 | |
148 private final List getList() { | |
149 getterCalled(); | |
150 return doGetList(); | |
151 } | |
152 | |
153 final List doGetList() { | |
154 if (dirty) { | |
155 // This line will do the following: | |
156 // - Run the calculate method | |
157 // - While doing so, add any observable that is touched to the | |
158 // dependencies list | |
159 IObservable[] newDependencies = ObservableTracker.runAndMonitor( | |
160 privateInterface, privateInterface, null); | |
161 | |
162 // If any dependencies are stale, a stale event will be fired here | |
163 // even if we were already stale before recomputing. This is in case | |
164 // clients assume that a list change is indicative of non-staleness. | |
165 stale = false; | |
166 for (int i = 0; i < newDependencies.length; i++) { | |
167 if (newDependencies[i].isStale()) { | |
168 makeStale(); | |
169 break; | |
170 } | |
171 } | |
172 | |
173 if (!stale) { | |
174 for (int i = 0; i < newDependencies.length; i++) { | |
175 newDependencies[i].addStaleListener(privateInterface); | |
176 } | |
177 } | |
178 | |
179 dependencies = newDependencies; | |
180 | |
181 dirty = false; | |
182 } | |
183 | |
184 return cachedList; | |
185 } | |
186 | |
187 private void getterCalled() { | |
188 ObservableTracker.getterCalled(this); | |
189 } | |
190 | |
191 /** | |
192 * Subclasses must override this method to calculate the list contents. | |
193 * | |
194 * @return the object's list. | |
195 */ | |
196 protected abstract List calculate(); | |
197 | |
198 private void makeDirty() { | |
199 if (!dirty) { | |
200 dirty = true; | |
201 | |
202 makeStale(); | |
203 | |
204 stopListening(); | |
205 | |
206 // copy the old list | |
207 final List oldList = new ArrayList(cachedList); | |
208 // Fire the "dirty" event. This implementation recomputes the new | |
209 // list lazily. | |
210 fireListChange(new class() ListDiff { | |
211 ListDiffEntry[] differences; | |
212 | |
213 public ListDiffEntry[] getDifferences() { | |
214 if (differences is null) | |
215 differences = Diffs.computeListDiff(oldList, getList()) | |
216 .getDifferences(); | |
217 return differences; | |
218 } | |
219 }); | |
220 } | |
221 } | |
222 | |
223 private void stopListening() { | |
224 if (dependencies !is null) { | |
225 for (int i = 0; i < dependencies.length; i++) { | |
226 IObservable observable = dependencies[i]; | |
227 | |
228 observable.removeChangeListener(privateInterface); | |
229 observable.removeStaleListener(privateInterface); | |
230 } | |
231 dependencies = null; | |
232 } | |
233 } | |
234 | |
235 private void makeStale() { | |
236 if (!stale) { | |
237 stale = true; | |
238 fireStale(); | |
239 } | |
240 } | |
241 | |
242 public bool isStale() { | |
243 // recalculate list if dirty, to ensure staleness is correct. | |
244 getList(); | |
245 return stale; | |
246 } | |
247 | |
248 public Object getElementType() { | |
249 return elementType; | |
250 } | |
251 | |
252 public synchronized void addChangeListener(IChangeListener listener) { | |
253 super.addChangeListener(listener); | |
254 // If somebody is listening, we need to make sure we attach our own | |
255 // listeners | |
256 computeListForListeners(); | |
257 } | |
258 | |
259 public synchronized void addListChangeListener(IListChangeListener listener) { | |
260 super.addListChangeListener(listener); | |
261 // If somebody is listening, we need to make sure we attach our own | |
262 // listeners | |
263 computeListForListeners(); | |
264 } | |
265 | |
266 private void computeListForListeners() { | |
267 // Some clients just add a listener and expect to get notified even if | |
268 // they never called getValue(), so we have to call getValue() ourselves | |
269 // here to be sure. Need to be careful about realms though, this method | |
270 // can be called outside of our realm. | |
271 // See also bug 198211. If a client calls this outside of our realm, | |
272 // they may receive change notifications before the runnable below has | |
273 // been executed. It is their job to figure out what to do with those | |
274 // notifications. | |
275 getRealm().exec(new class() Runnable { | |
276 public void run() { | |
277 if (dependencies is null) { | |
278 // We are not currently listening. | |
279 // But someone is listening for changes. Call getValue() | |
280 // to make sure we start listening to the observables we | |
281 // depend on. | |
282 getList(); | |
283 } | |
284 } | |
285 }); | |
286 } | |
287 | |
288 public synchronized void dispose() { | |
289 stopListening(); | |
290 super.dispose(); | |
291 } | |
292 } |