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