comparison org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Realm.d @ 95:6208d4f6a277

Added trees for databinding.beans and observable
author Frank Benoit <benoit@tionex.de>
date Tue, 21 Apr 2009 10:55:51 +0200
parents
children b74ac5dfcc06
comparison
equal deleted inserted replaced
94:1d37a7813832 95:6208d4f6a277
1 /*******************************************************************************
2 * Copyright (c) 2006, 2007 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 168153
11 *******************************************************************************/
12
13 package org.eclipse.core.databinding.observable;
14
15 import org.eclipse.core.databinding.Binding;
16 import org.eclipse.core.databinding.util.Policy;
17 import org.eclipse.core.internal.databinding.Queue;
18 import org.eclipse.core.runtime.ISafeRunnable;
19 import org.eclipse.core.runtime.IStatus;
20 import org.eclipse.core.runtime.SafeRunner;
21 import org.eclipse.core.runtime.Status;
22
23 /**
24 * A realm defines a context from which objects implementing {@link IObservable}
25 * must be accessed, and on which these objects will notify their listeners. To
26 * bridge between observables from different realms, subclasses of
27 * {@link Binding} can be used.
28 * <p>
29 * A block of code is said to be executing within a realm if calling
30 * {@link #isCurrent()} from that block returns true. Code reached by calling
31 * methods from that block will execute within the same realm, with the
32 * exception of methods on this class that can be used to execute code within a
33 * specific realm. Clients can use {@link #syncExeccast(Runnable)},
34 * {@link #asyncExeccast(Runnable)}, or {@link #execcast(Runnable)} to execute a
35 * runnable within this realm. Note that using {@link #syncExeccast(Runnable)} can
36 * lead to deadlocks and should be avoided if the current thread holds any
37 * locks.
38 * </p>
39 * <p>
40 * It is instructive to think about possible implementations of Realm: It can be
41 * based on executing on a designated thread such as a UI thread, or based on
42 * holding a lock. In the former case, calling syncExec on a realm that is not
43 * the current realm will execute the given runnable on a different thread (the
44 * designated thread). In the latter case, calling syncExec may execute the
45 * given runnable on the calling thread, but calling
46 * {@link #asyncExeccast(Runnable)} will execute the given runnable on a different
47 * thread. Therefore, no assumptions can be made about the thread that will
48 * execute arguments to {@link #asyncExeccast(Runnable)},
49 * {@link #syncExeccast(Runnable)}, or {@link #execcast(Runnable)}.
50 * </p>
51 * <p>
52 * It is possible that a block of code is executing within more than one realm.
53 * This can happen for implementations of Realm that are based on holding a lock
54 * but don't use a separate thread to run runnables given to
55 * {@link #syncExeccast(Runnable)}. Realm implementations of this kind should be
56 * appropriately documented because it increases the opportunity for deadlock.
57 * </p>
58 * <p>
59 * Some implementations of {@link IObservable} provide constructors which do not
60 * take a Realm argument and are specified to create the observable instance
61 * with the current default realm. The default realm can be set for the
62 * currently executing thread by using {@link #runWithDefault(Realm, Runnable)}.
63 * Note that the default realm does not have to be the current realm.
64 * </p>
65 * <p>
66 * Subclasses must override at least one of asyncExec()/syncExec(). For realms
67 * based on a designated thread, it may be easier to implement asyncExec and
68 * keep the default implementation of syncExec. For realms based on holding a
69 * lock, it may be easier to implement syncExec and keep the default
70 * implementation of asyncExec.
71 * </p>
72 *
73 * @since 1.0
74 *
75 * @see IObservable
76 */
77 public abstract class Realm {
78
79 private static ThreadLocal defaultRealm = new ThreadLocal();
80
81 /**
82 * Returns the default realm for the calling thread, or <code>null</code>
83 * if no default realm has been set.
84 *
85 * @return the default realm, or <code>null</code>
86 */
87 public static Realm getDefault() {
88 return cast(Realm) defaultRealm.get();
89 }
90
91 /**
92 * Sets the default realm for the calling thread, returning the current
93 * default thread. This method is inherently unsafe, it is recommended to
94 * use {@link #runWithDefault(Realm, Runnable)} instead. This method is
95 * exposed to subclasses to facilitate testing.
96 *
97 * @param realm
98 * the new default realm, or <code>null</code>
99 * @return the previous default realm, or <code>null</code>
100 */
101 protected static Realm setDefault(Realm realm) {
102 Realm oldValue = getDefault();
103 defaultRealm.set(realm);
104 return oldValue;
105 }
106
107 /**
108 * @return true if the caller is executing in this realm. This method must
109 * not have side-effects (such as, for example, implicitly placing
110 * the caller in this realm).
111 */
112 abstract public bool isCurrent();
113
114 private Thread workerThread;
115
116 Queue workQueue = new Queue();
117
118 /**
119 * Runs the given runnable. If an exception occurs within the runnable, it
120 * is logged and not re-thrown. If the runnable implements
121 * {@link ISafeRunnable}, the exception is passed to its
122 * <code>handleException<code> method.
123 *
124 * @param runnable
125 */
126 protected static void safeRun(final Runnable runnable) {
127 ISafeRunnable safeRunnable;
128 if ( null !is cast(ISafeRunnable)runnable ) {
129 safeRunnable = cast(ISafeRunnable) runnable;
130 } else {
131 safeRunnable = new class() ISafeRunnable {
132 public void handleException(Throwable exception) {
133 Policy
134 .getLog()
135 .log(
136 new Status(
137 IStatus.ERROR,
138 Policy.JFACE_DATABINDING,
139 IStatus.OK,
140 "Unhandled exception: " + exception.getMessage(), exception)); //$NON-NLS-1$
141 }
142 public void run() {
143 runnable.run();
144 }
145 };
146 }
147 SafeRunner.run(safeRunnable);
148 }
149
150 /**
151 * Causes the <code>run()</code> method of the runnable to be invoked from
152 * within this realm. If the caller is executing in this realm, the
153 * runnable's run method is invoked directly, otherwise it is run at the
154 * next reasonable opportunity using asyncExec.
155 * <p>
156 * If the given runnable is an instance of {@link ISafeRunnable}, its
157 * exception handler method will be called if any exceptions occur while
158 * running it. Otherwise, the exception will be logged.
159 * </p>
160 *
161 * @param runnable
162 */
163 public void exec(Runnable runnable) {
164 if (isCurrent()) {
165 safeRun(runnable);
166 } else {
167 asyncExec(runnable);
168 }
169 }
170
171 /**
172 * Causes the <code>run()</code> method of the runnable to be invoked from
173 * within this realm at the next reasonable opportunity. The caller of this
174 * method continues to run in parallel, and is not notified when the
175 * runnable has completed.
176 * <p>
177 * If the given runnable is an instance of {@link ISafeRunnable}, its
178 * exception handler method will be called if any exceptions occur while
179 * running it. Otherwise, the exception will be logged.
180 * </p>
181 * <p>
182 * Subclasses should use {@link #safeRuncast(Runnable)} to run the runnable.
183 * </p>
184 *
185 * @param runnable
186 */
187 public void asyncExec(Runnable runnable) {
188 synchronized (workQueue) {
189 ensureWorkerThreadIsRunning();
190 workQueue.enqueue(runnable);
191 workQueue.notifyAll();
192 }
193 }
194
195 /**
196 *
197 */
198 private void ensureWorkerThreadIsRunning() {
199 if (workerThread is null) {
200 workerThread = new class() Thread {
201 public void run() {
202 try {
203 while (true) {
204 Runnable work = null;
205 synchronized (workQueue) {
206 while (workQueue.isEmpty()) {
207 workQueue.wait();
208 }
209 work = cast(Runnable) workQueue.dequeue();
210 }
211 syncExec(work);
212 }
213 } catch (InterruptedException e) {
214 // exit
215 }
216 }
217 };
218 workerThread.start();
219 }
220 }
221
222 /**
223 * Causes the <code>run()</code> method of the runnable to be invoked from
224 * within this realm at the next reasonable opportunity. This method is
225 * blocking the caller until the runnable completes.
226 * <p>
227 * If the given runnable is an instance of {@link ISafeRunnable}, its
228 * exception handler method will be called if any exceptions occur while
229 * running it. Otherwise, the exception will be logged.
230 * </p>
231 * <p>
232 * Subclasses should use {@link #safeRuncast(Runnable)} to run the runnable.
233 * </p>
234 * <p>
235 * Note: This class is not meant to be called by clients and therefore has
236 * only protected access.
237 * </p>
238 *
239 * @param runnable
240 */
241 protected void syncExec(Runnable runnable) {
242 SyncRunnable syncRunnable = new SyncRunnable(runnable);
243 asyncExec(syncRunnable);
244 synchronized (syncRunnable) {
245 while (!syncRunnable.hasRun) {
246 try {
247 syncRunnable.wait();
248 } catch (InterruptedException e) {
249 Thread.currentThread().interrupt();
250 }
251 }
252 }
253 }
254
255 static class SyncRunnable : Runnable {
256 bool hasRun = false;
257
258 private Runnable runnable;
259
260 this(Runnable runnable) {
261 this.runnable = runnable;
262 }
263
264 public void run() {
265 try {
266 safeRun(runnable);
267 } finally {
268 synchronized (this) {
269 hasRun = true;
270 this.notifyAll();
271 }
272 }
273 }
274 }
275
276 /**
277 * Sets the provided <code>realm</code> as the default for the duration of
278 * {@link Runnable#run()} and resets the previous realm after completion.
279 * Note that this will not set the given realm as the current realm.
280 *
281 * @param realm
282 * @param runnable
283 */
284 public static void runWithDefault(Realm realm, Runnable runnable) {
285 Realm oldRealm = Realm.getDefault();
286 try {
287 defaultRealm.set(realm);
288 runnable.run();
289 } finally {
290 defaultRealm.set(oldRealm);
291 }
292 }
293 }