1 /******************************************************************************* 2 * Copyright (c) 2006, 2015 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Brad Reynolds - bug 168153 14 * Boris Bokowski - bug 245647 15 * Stefan Xenos <sxenos@gmail.com> - Bug 335792 16 *******************************************************************************/ 17 18 package org.eclipse.core.databinding.observable; 19 20 import java.util.Timer; 21 import java.util.TimerTask; 22 23 import org.eclipse.core.databinding.util.Policy; 24 import org.eclipse.core.internal.databinding.observable.Queue; 25 import org.eclipse.core.runtime.ISafeRunnable; 26 import org.eclipse.core.runtime.IStatus; 27 import org.eclipse.core.runtime.SafeRunner; 28 import org.eclipse.core.runtime.Status; 29 30 /** 31 * A realm defines a context from which objects implementing {@link IObservable} 32 * must be accessed, and on which these objects will notify their listeners. To 33 * bridge between observables from different realms, subclasses of 34 * <code>Binding</code> can be used. 35 * <p> 36 * A block of code is said to be executing within a realm if calling 37 * {@link #isCurrent()} from that block returns true. Code reached by calling 38 * methods from that block will execute within the same realm, with the 39 * exception of methods on this class that can be used to execute code within a 40 * specific realm. Clients can use {@link #syncExec(Runnable)}, 41 * {@link #asyncExec(Runnable)}, or {@link #exec(Runnable)} to execute a 42 * runnable within this realm. Note that using {@link #syncExec(Runnable)} can 43 * lead to deadlocks and should be avoided if the current thread holds any 44 * locks. 45 * </p> 46 * <p> 47 * It is instructive to think about possible implementations of Realm: It can be 48 * based on executing on a designated thread such as a UI thread, or based on 49 * holding a lock. In the former case, calling syncExec on a realm that is not 50 * the current realm will execute the given runnable on a different thread (the 51 * designated thread). In the latter case, calling syncExec may execute the 52 * given runnable on the calling thread, but calling 53 * {@link #asyncExec(Runnable)} will execute the given runnable on a different 54 * thread. Therefore, no assumptions can be made about the thread that will 55 * execute arguments to {@link #asyncExec(Runnable)}, 56 * {@link #syncExec(Runnable)}, or {@link #exec(Runnable)}. 57 * </p> 58 * <p> 59 * It is possible that a block of code is executing within more than one realm. 60 * This can happen for implementations of Realm that are based on holding a lock 61 * but don't use a separate thread to run runnables given to 62 * {@link #syncExec(Runnable)}. Realm implementations of this kind should be 63 * appropriately documented because it increases the opportunity for deadlock. 64 * </p> 65 * <p> 66 * Some implementations of {@link IObservable} provide constructors which do not 67 * take a Realm argument and are specified to create the observable instance 68 * with the current default realm. The default realm can be set for the 69 * currently executing thread by using {@link #runWithDefault(Realm, Runnable)}. 70 * Note that the default realm does not have to be the current realm. 71 * </p> 72 * <p> 73 * Subclasses must override at least one of asyncExec()/syncExec(). For realms 74 * based on a designated thread, it may be easier to implement asyncExec and 75 * keep the default implementation of syncExec. For realms based on holding a 76 * lock, it may be easier to implement syncExec and keep the default 77 * implementation of asyncExec. 78 * </p> 79 * 80 * @since 1.0 81 * 82 * @see IObservable 83 */ 84 public abstract class Realm { 85 86 private static ThreadLocal<Realm> defaultRealm = new ThreadLocal<>(); 87 88 /** 89 * Returns the default realm for the calling thread, or <code>null</code> if 90 * no default realm has been set. 91 * 92 * @return the default realm, or <code>null</code> 93 */ getDefault()94 public static Realm getDefault() { 95 return defaultRealm.get(); 96 } 97 98 /** 99 * Sets the default realm for the calling thread, returning the current 100 * default thread. This method is inherently unsafe, it is recommended to 101 * use {@link #runWithDefault(Realm, Runnable)} instead. This method is 102 * exposed to subclasses to facilitate testing. 103 * 104 * @param realm 105 * the new default realm, or <code>null</code> 106 * @return the previous default realm, or <code>null</code> 107 */ setDefault(Realm realm)108 protected static Realm setDefault(Realm realm) { 109 Realm oldValue = getDefault(); 110 defaultRealm.set(realm); 111 return oldValue; 112 } 113 114 /** 115 * @return true if the caller is executing in this realm. This method must 116 * not have side-effects (such as, for example, implicitly placing 117 * the caller in this realm). 118 */ isCurrent()119 abstract public boolean isCurrent(); 120 121 private Thread workerThread; 122 123 private volatile Timer timer; 124 125 Queue workQueue = new Queue(); 126 127 /** 128 * Runs the given runnable. If an exception occurs within the runnable, it is 129 * logged and not re-thrown. If the runnable implements {@link ISafeRunnable}, 130 * the exception is passed to its <code>handleException</code> method. 131 * 132 * @param runnable {@link Runnable} to execute 133 */ safeRun(final Runnable runnable)134 protected static void safeRun(final Runnable runnable) { 135 ISafeRunnable safeRunnable; 136 if (runnable instanceof ISafeRunnable) { 137 safeRunnable = (ISafeRunnable) runnable; 138 } else { 139 safeRunnable = new ISafeRunnable() { 140 @Override 141 public void handleException(Throwable exception) { 142 Policy.getLog() 143 .log(new Status( 144 IStatus.ERROR, 145 Policy.JFACE_DATABINDING, 146 IStatus.OK, 147 "Unhandled exception: " + exception.getMessage(), exception)); //$NON-NLS-1$ 148 } 149 @Override 150 public void run() throws Exception { 151 runnable.run(); 152 } 153 }; 154 } 155 SafeRunner.run(safeRunnable); 156 } 157 158 /** 159 * Causes the <code>run()</code> method of the runnable to be invoked from 160 * within this realm. If the caller is executing in this realm, the 161 * runnable's run method is invoked directly, otherwise it is run at the 162 * next reasonable opportunity using asyncExec. 163 * <p> 164 * If the given runnable is an instance of {@link ISafeRunnable}, its 165 * exception handler method will be called if any exceptions occur while 166 * running it. Otherwise, the exception will be logged. 167 * </p> 168 * 169 * @param runnable {@link Runnable} to execute 170 */ exec(Runnable runnable)171 public void exec(Runnable runnable) { 172 if (isCurrent()) { 173 safeRun(runnable); 174 } else { 175 asyncExec(runnable); 176 } 177 } 178 179 /** 180 * Causes the <code>run()</code> method of the runnable to be invoked from 181 * within this realm at the next reasonable opportunity. The caller of this 182 * method continues to run in parallel, and is not notified when the 183 * runnable has completed. 184 * <p> 185 * If the given runnable is an instance of {@link ISafeRunnable}, its 186 * exception handler method will be called if any exceptions occur while 187 * running it. Otherwise, the exception will be logged. 188 * </p> 189 * <p> 190 * Subclasses should use {@link #safeRun(Runnable)} to run the runnable. 191 * </p> 192 * 193 * @param runnable {@link Runnable} to execute 194 */ asyncExec(Runnable runnable)195 public void asyncExec(Runnable runnable) { 196 synchronized (workQueue) { 197 ensureWorkerThreadIsRunning(); 198 workQueue.enqueue(runnable); 199 workQueue.notifyAll(); 200 } 201 } 202 203 /** 204 * Causes the <code>run()</code> method of the runnable to be invoked from 205 * within this realm after the specified number of milliseconds have 206 * elapsed. If milliseconds is less than zero, the runnable is not executed. 207 * The caller of this method continues to run in parallel, and is not 208 * notified when the runnable has completed. 209 * <p> 210 * If the given runnable is an instance of {@link ISafeRunnable}, its 211 * exception handler method will be called if any exceptions occur while 212 * running it. Otherwise, the exception will be logged. 213 * </p> 214 * <p> 215 * Subclasses should use {@link #safeRun(Runnable)} to run the runnable. 216 * </p> 217 * 218 * @param milliseconds wait time before executing the runnable 219 * @param runnable {@link Runnable} to execute 220 * @since 1.2 221 */ timerExec(int milliseconds, final Runnable runnable)222 public void timerExec(int milliseconds, final Runnable runnable) { 223 if (milliseconds < 0) { 224 return; 225 } else if (milliseconds == 0) { 226 asyncExec(runnable); 227 } else { 228 synchronized (workQueue) { 229 if (timer == null) { 230 timer = new Timer(true); 231 } 232 timer.schedule(new TimerTask() { 233 @Override 234 public void run() { 235 asyncExec(runnable); 236 } 237 }, milliseconds); 238 } 239 } 240 241 } 242 243 /** 244 * 245 */ ensureWorkerThreadIsRunning()246 private void ensureWorkerThreadIsRunning() { 247 if (workerThread == null) { 248 workerThread = new Thread() { 249 @Override 250 public void run() { 251 try { 252 while (true) { 253 Runnable work = null; 254 synchronized (workQueue) { 255 while (workQueue.isEmpty()) { 256 workQueue.wait(); 257 } 258 work = (Runnable) workQueue.dequeue(); 259 } 260 syncExec(work); 261 } 262 } catch (InterruptedException e) { 263 // exit 264 } 265 } 266 }; 267 workerThread.start(); 268 } 269 } 270 271 /** 272 * Causes the <code>run()</code> method of the runnable to be invoked from 273 * within this realm at the next reasonable opportunity. This method is 274 * blocking the caller until the runnable completes. 275 * <p> 276 * If the given runnable is an instance of {@link ISafeRunnable}, its 277 * exception handler method will be called if any exceptions occur while 278 * running it. Otherwise, the exception will be logged. 279 * </p> 280 * <p> 281 * Subclasses should use {@link #safeRun(Runnable)} to run the runnable. 282 * </p> 283 * <p> 284 * Note: This class is not meant to be called by clients and therefore has 285 * only protected access. 286 * </p> 287 * 288 * @param runnable {@link Runnable} to execute 289 */ syncExec(Runnable runnable)290 protected void syncExec(Runnable runnable) { 291 SyncRunnable syncRunnable = new SyncRunnable(runnable); 292 asyncExec(syncRunnable); 293 synchronized (syncRunnable) { 294 while (!syncRunnable.hasRun) { 295 try { 296 syncRunnable.wait(); 297 } catch (InterruptedException e) { 298 Thread.currentThread().interrupt(); 299 } 300 } 301 } 302 } 303 304 static class SyncRunnable implements Runnable { 305 boolean hasRun = false; 306 307 private Runnable runnable; 308 SyncRunnable(Runnable runnable)309 SyncRunnable(Runnable runnable) { 310 this.runnable = runnable; 311 } 312 313 @Override run()314 public void run() { 315 try { 316 safeRun(runnable); 317 } finally { 318 synchronized (this) { 319 hasRun = true; 320 this.notifyAll(); 321 } 322 } 323 } 324 } 325 326 /** 327 * Sets the provided <code>realm</code> as the default for the duration of 328 * {@link Runnable#run()} and resets the previous realm after completion. 329 * Note that this will not set the given realm as the current realm. 330 * 331 * @param realm default realm for the runnable 332 * @param runnable {@link Runnable} to execute 333 */ runWithDefault(Realm realm, Runnable runnable)334 public static void runWithDefault(Realm realm, Runnable runnable) { 335 Realm oldRealm = Realm.getDefault(); 336 try { 337 defaultRealm.set(realm); 338 runnable.run(); 339 } finally { 340 defaultRealm.set(oldRealm); 341 } 342 } 343 } 344