Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 9d540f6cbdd82a40794faff10cd08e65be892c2e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
/*******************************************************************************
 * Copyright (c) 2006, 2009 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Brad Reynolds - bug 168153
 *     Boris Bokowski - bug 245647
 *******************************************************************************/

package org.eclipse.core.databinding.observable;

import java.util.Timer;
import java.util.TimerTask;

import org.eclipse.core.databinding.util.Policy;
import org.eclipse.core.internal.databinding.observable.Queue;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;

/**
 * A realm defines a context from which objects implementing {@link IObservable}
 * must be accessed, and on which these objects will notify their listeners. To
 * bridge between observables from different realms, subclasses of
 * <code>Binding</code> can be used.
 * <p>
 * A block of code is said to be executing within a realm if calling
 * {@link #isCurrent()} from that block returns true. Code reached by calling
 * methods from that block will execute within the same realm, with the
 * exception of methods on this class that can be used to execute code within a
 * specific realm. Clients can use {@link #syncExec(Runnable)},
 * {@link #asyncExec(Runnable)}, or {@link #exec(Runnable)} to execute a
 * runnable within this realm. Note that using {@link #syncExec(Runnable)} can
 * lead to deadlocks and should be avoided if the current thread holds any
 * locks.
 * </p>
 * <p>
 * It is instructive to think about possible implementations of Realm: It can be
 * based on executing on a designated thread such as a UI thread, or based on
 * holding a lock. In the former case, calling syncExec on a realm that is not
 * the current realm will execute the given runnable on a different thread (the
 * designated thread). In the latter case, calling syncExec may execute the
 * given runnable on the calling thread, but calling
 * {@link #asyncExec(Runnable)} will execute the given runnable on a different
 * thread. Therefore, no assumptions can be made about the thread that will
 * execute arguments to {@link #asyncExec(Runnable)},
 * {@link #syncExec(Runnable)}, or {@link #exec(Runnable)}.
 * </p>
 * <p>
 * It is possible that a block of code is executing within more than one realm.
 * This can happen for implementations of Realm that are based on holding a lock
 * but don't use a separate thread to run runnables given to
 * {@link #syncExec(Runnable)}. Realm implementations of this kind should be
 * appropriately documented because it increases the opportunity for deadlock.
 * </p>
 * <p>
 * Some implementations of {@link IObservable} provide constructors which do not
 * take a Realm argument and are specified to create the observable instance
 * with the current default realm. The default realm can be set for the
 * currently executing thread by using {@link #runWithDefault(Realm, Runnable)}.
 * Note that the default realm does not have to be the current realm.
 * </p>
 * <p>
 * Subclasses must override at least one of asyncExec()/syncExec(). For realms
 * based on a designated thread, it may be easier to implement asyncExec and
 * keep the default implementation of syncExec. For realms based on holding a
 * lock, it may be easier to implement syncExec and keep the default
 * implementation of asyncExec.
 * </p>
 * 
 * @since 1.0
 * 
 * @see IObservable
 */
public abstract class Realm {

	private static ThreadLocal defaultRealm = new ThreadLocal();

	/**
	 * Returns the default realm for the calling thread, or <code>null</code>
	 * if no default realm has been set.
	 * 
	 * @return the default realm, or <code>null</code>
	 */
	public static Realm getDefault() {
		return (Realm) defaultRealm.get();
	}
	
	/**
	 * Sets the default realm for the calling thread, returning the current
	 * default thread. This method is inherently unsafe, it is recommended to
	 * use {@link #runWithDefault(Realm, Runnable)} instead. This method is
	 * exposed to subclasses to facilitate testing.
	 * 
	 * @param realm
	 *            the new default realm, or <code>null</code>
	 * @return the previous default realm, or <code>null</code>
	 */
	protected static Realm setDefault(Realm realm) {
		Realm oldValue = getDefault();
		defaultRealm.set(realm);
		return oldValue;
	}

	/**
	 * @return true if the caller is executing in this realm. This method must
	 *         not have side-effects (such as, for example, implicitly placing
	 *         the caller in this realm).
	 */
	abstract public boolean isCurrent();

	private Thread workerThread;
	
	private volatile Timer timer;

	Queue workQueue = new Queue();
	
	/**
	 * Runs the given runnable. If an exception occurs within the runnable, it
	 * is logged and not re-thrown. If the runnable implements
	 * {@link ISafeRunnable}, the exception is passed to its
	 * <code>handleException<code> method.
	 * 
	 * @param runnable
	 */
	protected static void safeRun(final Runnable runnable) {
		ISafeRunnable safeRunnable;
		if (runnable instanceof ISafeRunnable) {
			safeRunnable = (ISafeRunnable) runnable;
		} else {
			safeRunnable = new ISafeRunnable() {
				public void handleException(Throwable exception) {
					Policy
							.getLog()
							.log(
									new Status(
											IStatus.ERROR,
											Policy.JFACE_DATABINDING,
											IStatus.OK,
											"Unhandled exception: " + exception.getMessage(), exception)); //$NON-NLS-1$
				}
				public void run() throws Exception {
					runnable.run();
				}
			};
		}
		SafeRunner.run(safeRunnable);
	}

	/**
	 * Causes the <code>run()</code> method of the runnable to be invoked from
	 * within this realm. If the caller is executing in this realm, the
	 * runnable's run method is invoked directly, otherwise it is run at the
	 * next reasonable opportunity using asyncExec.
	 * <p>
	 * If the given runnable is an instance of {@link ISafeRunnable}, its
	 * exception handler method will be called if any exceptions occur while
	 * running it. Otherwise, the exception will be logged.
	 * </p>
	 * 
	 * @param runnable
	 */
	public void exec(Runnable runnable) {
		if (isCurrent()) {
			safeRun(runnable);
		} else {
			asyncExec(runnable);
		}
	}

	/**
	 * Causes the <code>run()</code> method of the runnable to be invoked from
	 * within this realm at the next reasonable opportunity. The caller of this
	 * method continues to run in parallel, and is not notified when the
	 * runnable has completed.
	 * <p>
	 * If the given runnable is an instance of {@link ISafeRunnable}, its
	 * exception handler method will be called if any exceptions occur while
	 * running it. Otherwise, the exception will be logged.
	 * </p>
	 * <p>
	 * Subclasses should use {@link #safeRun(Runnable)} to run the runnable.
	 * </p>
	 * 
	 * @param runnable
	 */
	public void asyncExec(Runnable runnable) {
		synchronized (workQueue) {
			ensureWorkerThreadIsRunning();
			workQueue.enqueue(runnable);
			workQueue.notifyAll();
		}
	}

	/**
	 * Causes the <code>run()</code> method of the runnable to be invoked from
	 * within this realm after the specified number of milliseconds have
	 * elapsed. If milliseconds is less than zero, the runnable is not executed.
	 * The caller of this method continues to run in parallel, and is not
	 * notified when the runnable has completed.
	 * <p>
	 * If the given runnable is an instance of {@link ISafeRunnable}, its
	 * exception handler method will be called if any exceptions occur while
	 * running it. Otherwise, the exception will be logged.
	 * </p>
	 * <p>
	 * Subclasses should use {@link #safeRun(Runnable)} to run the runnable.
	 * </p>
	 * 
	 * @param milliseconds
	 * @param runnable
	 * @since 1.2
	 */
	public void timerExec(int milliseconds, final Runnable runnable) {
		if (milliseconds < 0) {
			return;
		} else if (milliseconds == 0) {
			asyncExec(runnable);
		} else {
			synchronized (workQueue) {
				if (timer == null) {
					timer = new Timer(true);
				}
				timer.schedule(new TimerTask() {
					public void run() {
						asyncExec(runnable);
					}
				}, milliseconds);
			}
		}

	}

	/**
	 * 
	 */
	private void ensureWorkerThreadIsRunning() {
		if (workerThread == null) {
			workerThread = new Thread() {
				public void run() {
					try {
						while (true) {
							Runnable work = null;
							synchronized (workQueue) {
								while (workQueue.isEmpty()) {
									workQueue.wait();
								}
								work = (Runnable) workQueue.dequeue();
							}
							syncExec(work);
						}
					} catch (InterruptedException e) {
						// exit
					}
				}
			};
			workerThread.start();
		}
	}

	/**
	 * Causes the <code>run()</code> method of the runnable to be invoked from
	 * within this realm at the next reasonable opportunity. This method is
	 * blocking the caller until the runnable completes.
	 * <p>
	 * If the given runnable is an instance of {@link ISafeRunnable}, its
	 * exception handler method will be called if any exceptions occur while
	 * running it. Otherwise, the exception will be logged.
	 * </p>
	 * <p>
	 * Subclasses should use {@link #safeRun(Runnable)} to run the runnable.
	 * </p>
	 * <p>
	 * Note: This class is not meant to be called by clients and therefore has
	 * only protected access.
	 * </p>
	 * 
	 * @param runnable
	 */
	protected void syncExec(Runnable runnable) {
		SyncRunnable syncRunnable = new SyncRunnable(runnable);
		asyncExec(syncRunnable);
		synchronized (syncRunnable) {
			while (!syncRunnable.hasRun) {
				try {
					syncRunnable.wait();
				} catch (InterruptedException e) {
					Thread.currentThread().interrupt();
				}
			}
		}
	}

	static class SyncRunnable implements Runnable {
		boolean hasRun = false;

		private Runnable runnable;

		SyncRunnable(Runnable runnable) {
			this.runnable = runnable;
		}

		public void run() {
			try {
				safeRun(runnable);
			} finally {
				synchronized (this) {
					hasRun = true;
					this.notifyAll();
				}
			}
		}
	}

	/**
	 * Sets the provided <code>realm</code> as the default for the duration of
	 * {@link Runnable#run()} and resets the previous realm after completion.
	 * Note that this will not set the given realm as the current realm.
	 * 
	 * @param realm
	 * @param runnable
	 */
	public static void runWithDefault(Realm realm, Runnable runnable) {
		Realm oldRealm = Realm.getDefault();
		try {
			defaultRealm.set(realm);
			runnable.run();
		} finally {
			defaultRealm.set(oldRealm);
		}
	}
}

Back to the top