comparison org.eclipse.core.databinding/src/org/eclipse/core/databinding/observable/value/ComputedValue.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 * Brad Reynolds - bug 116920
11 * Brad Reynolds - bug 147515
12 *******************************************************************************/
13 module org.eclipse.core.databinding.observable.value.ComputedValue;
14
15 import java.lang.all;
16
17 import org.eclipse.core.databinding.observable.ChangeEvent;
18 import org.eclipse.core.databinding.observable.IChangeListener;
19 import org.eclipse.core.databinding.observable.IObservable;
20 import org.eclipse.core.databinding.observable.IStaleListener;
21 import org.eclipse.core.databinding.observable.ObservableTracker;
22 import org.eclipse.core.databinding.observable.Realm;
23 import org.eclipse.core.databinding.observable.StaleEvent;
24
25 /**
26 * A Lazily calculated value that automatically computes and registers listeners
27 * on its dependencies as long as all of its dependencies are IObservable
28 * objects
29 * <p>
30 * This class is thread safe. All state accessing methods must be invoked from
31 * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
32 * listeners may be invoked from any thread.
33 * </p>
34 *
35 * @since 1.0
36 */
37 public abstract class ComputedValue : AbstractObservableValue {
38
39 private bool dirty = true;
40
41 private bool stale = false;
42
43 private Object cachedValue = null;
44
45 /**
46 * Array of observables this computed value depends on. This field has a
47 * value of <code>null</code> if we are not currently listening.
48 */
49 private IObservable[] dependencies = null;
50
51 /**
52 *
53 */
54 public this() {
55 this(Realm.getDefault(), null);
56 }
57
58 /**
59 * @param valueType
60 * can be <code>null</code>
61 */
62 public this(Object valueType) {
63 this(Realm.getDefault(), valueType);
64 }
65
66 /**
67 * @param realm
68 *
69 */
70 public this(Realm realm) {
71 this(realm, null);
72 }
73
74 /**
75 * @param realm
76 * @param valueType
77 */
78 public this(Realm realm, Object valueType) {
79 super(realm);
80 this.valueType = valueType;
81 }
82
83 /**
84 * Inner class that implements interfaces that we don't want to expose as
85 * public API. Each interface could have been implemented using a separate
86 * anonymous class, but we combine them here to reduce the memory overhead
87 * and number of classes.
88 *
89 * <p>
90 * The Runnable calls computeValue and stores the result in cachedValue.
91 * </p>
92 *
93 * <p>
94 * The IChangeListener stores each observable in the dependencies list. This
95 * is registered as the listener when calling ObservableTracker, to detect
96 * every observable that is used by computeValue.
97 * </p>
98 *
99 * <p>
100 * The IChangeListener is attached to every dependency.
101 * </p>
102 *
103 */
104 private class PrivateInterface : Runnable, IChangeListener,
105 IStaleListener {
106 public void run() {
107 cachedValue = calculate();
108 }
109
110 public void handleStale(StaleEvent event) {
111 if (!dirty && !stale) {
112 stale = true;
113 fireStale();
114 }
115 }
116
117 public void handleChange(ChangeEvent event) {
118 makeDirty();
119 }
120 }
121
122 private PrivateInterface privateInterface = new PrivateInterface();
123
124 private Object valueType;
125
126 protected final Object doGetValue() {
127 if (dirty) {
128 // This line will do the following:
129 // - Run the calculate method
130 // - While doing so, add any observable that is touched to the
131 // dependencies list
132 IObservable[] newDependencies = ObservableTracker.runAndMonitor(
133 privateInterface, privateInterface, null);
134
135 stale = false;
136 for (int i = 0; i < newDependencies.length; i++) {
137 IObservable observable = newDependencies[i];
138 // Add a change listener to the new dependency.
139 if (observable.isStale()) {
140 stale = true;
141 } else {
142 observable.addStaleListener(privateInterface);
143 }
144 }
145
146 dependencies = newDependencies;
147
148 dirty = false;
149 }
150
151 return cachedValue;
152 }
153
154 /**
155 * Subclasses must override this method to provide the object's value.
156 *
157 * @return the object's value
158 */
159 protected abstract Object calculate();
160
161 protected final void makeDirty() {
162 if (!dirty) {
163 dirty = true;
164
165 stopListening();
166
167 // copy the old value
168 final Object oldValue = cachedValue;
169 // Fire the "dirty" event. This implementation recomputes the new
170 // value lazily.
171 fireValueChange(new class() ValueDiff {
172
173 public Object getOldValue() {
174 return oldValue;
175 }
176
177 public Object getNewValue() {
178 return getValue();
179 }
180 });
181 }
182 }
183
184 /**
185 *
186 */
187 private void stopListening() {
188 // Stop listening for dependency changes.
189 if (dependencies !is null) {
190 for (int i = 0; i < dependencies.length; i++) {
191 IObservable observable = dependencies[i];
192
193 observable.removeChangeListener(privateInterface);
194 observable.removeStaleListener(privateInterface);
195 }
196 dependencies = null;
197 }
198 }
199
200 public bool isStale() {
201 // we need to recompute, otherwise staleness wouldn't mean anything
202 getValue();
203 return stale;
204 }
205
206 public Object getValueType() {
207 return valueType;
208 }
209
210 // this method exists here so that we can call it from the runnable below.
211 /**
212 * @since 1.1
213 */
214 protected bool hasListeners() {
215 return super.hasListeners();
216 }
217
218 public synchronized void addChangeListener(IChangeListener listener) {
219 super.addChangeListener(listener);
220 // If somebody is listening, we need to make sure we attach our own
221 // listeners
222 computeValueForListeners();
223 }
224
225 /**
226 * Some clients just add a listener and expect to get notified even if they
227 * never called getValue(), so we have to call getValue() ourselves here to
228 * be sure. Need to be careful about realms though, this method can be
229 * called outside of our realm. See also bug 198211. If a client calls this
230 * outside of our realm, they may receive change notifications before the
231 * runnable below has been executed. It is their job to figure out what to
232 * do with those notifications.
233 */
234 private void computeValueForListeners() {
235 getRealm().exec(new class() Runnable {
236 public void run() {
237 if (dependencies is null) {
238 // We are not currently listening.
239 if (hasListeners()) {
240 // But someone is listening for changes. Call getValue()
241 // to make sure we start listening to the observables we
242 // depend on.
243 getValue();
244 }
245 }
246 }
247 });
248 }
249
250 public synchronized void addValueChangeListener(
251 IValueChangeListener listener) {
252 super.addValueChangeListener(listener);
253 // If somebody is listening, we need to make sure we attach our own
254 // listeners
255 computeValueForListeners();
256 }
257
258 public synchronized void dispose() {
259 super.dispose();
260 stopListening();
261 }
262
263 }