J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved. |
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun in the LICENSE file that accompanied this code. |
| 10 | * |
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * version 2 for more details (a copy is included in the LICENSE file that |
| 15 | * accompanied this code). |
| 16 | * |
| 17 | * You should have received a copy of the GNU General Public License version |
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 20 | * |
| 21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any questions. |
| 24 | */ |
| 25 | |
| 26 | package sun.awt; |
| 27 | |
| 28 | import java.awt.AWTEvent; |
| 29 | import java.util.Collections; |
| 30 | import java.util.HashSet; |
| 31 | import java.util.IdentityHashMap; |
| 32 | import java.util.Map; |
| 33 | import java.util.logging.Logger; |
| 34 | |
| 35 | /** |
| 36 | * This class is to let AWT shutdown automatically when a user is done |
| 37 | * with AWT. It tracks AWT state using the following parameters: |
| 38 | * <ul> |
| 39 | * <li><code>peerMap</code> - the map between the existing peer objects |
| 40 | * and their associated targets |
| 41 | * <li><code>toolkitThreadBusy</code> - whether the toolkit thread |
| 42 | * is waiting for a new native event to appear in its queue |
| 43 | * or is dispatching an event |
| 44 | * <li><code>busyThreadSet</code> - a set of all the event dispatch |
| 45 | * threads that are busy at this moment, i.e. those that are not |
| 46 | * waiting for a new event to appear in their event queue. |
| 47 | * </ul><p> |
| 48 | * AWT is considered to be in ready-to-shutdown state when |
| 49 | * <code>peerMap</code> is empty and <code>toolkitThreadBusy</code> |
| 50 | * is false and <code>busyThreadSet</code> is empty. |
| 51 | * The internal AWTAutoShutdown logic secures that the single non-daemon |
| 52 | * thread (<code>blockerThread</code>) is running when AWT is not in |
| 53 | * ready-to-shutdown state. This blocker thread is to prevent AWT from |
| 54 | * exiting since the toolkit thread is now daemon and all the event |
| 55 | * dispatch threads are started only when needed. Once it is detected |
| 56 | * that AWT is in ready-to-shutdown state this blocker thread waits |
| 57 | * for a certain timeout and if AWT state doesn't change during timeout |
| 58 | * this blocker thread terminates all the event dispatch threads and |
| 59 | * exits. |
| 60 | */ |
| 61 | public final class AWTAutoShutdown implements Runnable { |
| 62 | |
| 63 | private static final AWTAutoShutdown theInstance = new AWTAutoShutdown(); |
| 64 | |
| 65 | /** |
| 66 | * This lock object is used to synchronize shutdown operations. |
| 67 | */ |
| 68 | private final Object mainLock = new Object(); |
| 69 | |
| 70 | /** |
| 71 | * This lock object is to secure that when a new blocker thread is |
| 72 | * started it will be the first who acquire the main lock after |
| 73 | * the thread that created the new blocker released the main lock |
| 74 | * by calling lock.wait() to wait for the blocker to start. |
| 75 | */ |
| 76 | private final Object activationLock = new Object(); |
| 77 | |
| 78 | /** |
| 79 | * This set keeps references to all the event dispatch threads that |
| 80 | * are busy at this moment, i.e. those that are not waiting for a |
| 81 | * new event to appear in their event queue. |
| 82 | * Access is synchronized on the main lock object. |
| 83 | */ |
| 84 | private final HashSet busyThreadSet = new HashSet(7); |
| 85 | |
| 86 | /** |
| 87 | * Indicates whether the toolkit thread is waiting for a new native |
| 88 | * event to appear or is dispatching an event. |
| 89 | */ |
| 90 | private boolean toolkitThreadBusy = false; |
| 91 | |
| 92 | /** |
| 93 | * This is a map between components and their peers. |
| 94 | * we should work with in under activationLock&mainLock lock. |
| 95 | */ |
| 96 | private final Map peerMap = new IdentityHashMap(); |
| 97 | |
| 98 | /** |
| 99 | * References the alive non-daemon thread that is currently used |
| 100 | * for keeping AWT from exiting. |
| 101 | */ |
| 102 | private Thread blockerThread = null; |
| 103 | |
| 104 | /** |
| 105 | * We need this flag to secure that AWT state hasn't changed while |
| 106 | * we were waiting for the safety timeout to pass. |
| 107 | */ |
| 108 | private boolean timeoutPassed = false; |
| 109 | |
| 110 | /** |
| 111 | * Once we detect that AWT is ready to shutdown we wait for a certain |
| 112 | * timeout to pass before stopping event dispatch threads. |
| 113 | */ |
| 114 | private static final int SAFETY_TIMEOUT = 1000; |
| 115 | |
| 116 | /** |
| 117 | * Constructor method is intentionally made private to secure |
| 118 | * a single instance. Use getInstance() to reference it. |
| 119 | * |
| 120 | * @see AWTAutoShutdown#getInstance |
| 121 | */ |
| 122 | private AWTAutoShutdown() {} |
| 123 | |
| 124 | /** |
| 125 | * Returns reference to a single AWTAutoShutdown instance. |
| 126 | */ |
| 127 | public static AWTAutoShutdown getInstance() { |
| 128 | return theInstance; |
| 129 | } |
| 130 | |
| 131 | /** |
| 132 | * Notify that the toolkit thread is not waiting for a native event |
| 133 | * to appear in its queue. |
| 134 | * |
| 135 | * @see AWTAutoShutdown#notifyToolkitThreadFree |
| 136 | * @see AWTAutoShutdown#setToolkitBusy |
| 137 | * @see AWTAutoShutdown#isReadyToShutdown |
| 138 | */ |
| 139 | public static void notifyToolkitThreadBusy() { |
| 140 | getInstance().setToolkitBusy(true); |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * Notify that the toolkit thread is waiting for a native event |
| 145 | * to appear in its queue. |
| 146 | * |
| 147 | * @see AWTAutoShutdown#notifyToolkitThreadFree |
| 148 | * @see AWTAutoShutdown#setToolkitBusy |
| 149 | * @see AWTAutoShutdown#isReadyToShutdown |
| 150 | */ |
| 151 | public static void notifyToolkitThreadFree() { |
| 152 | getInstance().setToolkitBusy(false); |
| 153 | } |
| 154 | |
| 155 | /** |
| 156 | * Add a specified thread to the set of busy event dispatch threads. |
| 157 | * If this set already contains the specified thread, the call leaves |
| 158 | * this set unchanged and returns silently. |
| 159 | * |
| 160 | * @param thread thread to be added to this set, if not present. |
| 161 | * @see AWTAutoShutdown#notifyThreadFree |
| 162 | * @see AWTAutoShutdown#isReadyToShutdown |
| 163 | */ |
| 164 | public void notifyThreadBusy(final Thread thread) { |
| 165 | synchronized (activationLock) { |
| 166 | synchronized (mainLock) { |
| 167 | if (blockerThread == null) { |
| 168 | activateBlockerThread(); |
| 169 | } else if (isReadyToShutdown()) { |
| 170 | mainLock.notifyAll(); |
| 171 | timeoutPassed = false; |
| 172 | } |
| 173 | busyThreadSet.add(thread); |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | /** |
| 179 | * Remove a specified thread from the set of busy event dispatch threads. |
| 180 | * If this set doesn't contain the specified thread, the call leaves |
| 181 | * this set unchanged and returns silently. |
| 182 | * |
| 183 | * @param thread thread to be removed from this set, if present. |
| 184 | * @see AWTAutoShutdown#notifyThreadBusy |
| 185 | * @see AWTAutoShutdown#isReadyToShutdown |
| 186 | */ |
| 187 | public void notifyThreadFree(final Thread thread) { |
| 188 | synchronized (activationLock) { |
| 189 | synchronized (mainLock) { |
| 190 | busyThreadSet.remove(thread); |
| 191 | if (isReadyToShutdown()) { |
| 192 | mainLock.notifyAll(); |
| 193 | timeoutPassed = false; |
| 194 | } |
| 195 | } |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | /** |
| 200 | * Notify that the peermap has been updated, that means a new peer |
| 201 | * has been created or some existing peer has been disposed. |
| 202 | * |
| 203 | * @see AWTAutoShutdown#isReadyToShutdown |
| 204 | */ |
| 205 | void notifyPeerMapUpdated() { |
| 206 | synchronized (activationLock) { |
| 207 | synchronized (mainLock) { |
| 208 | if (!isReadyToShutdown() && blockerThread == null) { |
| 209 | activateBlockerThread(); |
| 210 | } else { |
| 211 | mainLock.notifyAll(); |
| 212 | timeoutPassed = false; |
| 213 | } |
| 214 | } |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | /** |
| 219 | * Determine whether AWT is currently in ready-to-shutdown state. |
| 220 | * AWT is considered to be in ready-to-shutdown state if |
| 221 | * <code>peerMap</code> is empty and <code>toolkitThreadBusy</code> |
| 222 | * is false and <code>busyThreadSet</code> is empty. |
| 223 | * |
| 224 | * @return true if AWT is in ready-to-shutdown state. |
| 225 | */ |
| 226 | private boolean isReadyToShutdown() { |
| 227 | return (!toolkitThreadBusy && |
| 228 | peerMap.isEmpty() && |
| 229 | busyThreadSet.isEmpty()); |
| 230 | } |
| 231 | |
| 232 | /** |
| 233 | * Notify about the toolkit thread state change. |
| 234 | * |
| 235 | * @param busy true if the toolkit thread state changes from idle |
| 236 | * to busy. |
| 237 | * @see AWTAutoShutdown#notifyToolkitThreadBusy |
| 238 | * @see AWTAutoShutdown#notifyToolkitThreadFree |
| 239 | * @see AWTAutoShutdown#isReadyToShutdown |
| 240 | */ |
| 241 | private void setToolkitBusy(final boolean busy) { |
| 242 | if (busy != toolkitThreadBusy) { |
| 243 | synchronized (activationLock) { |
| 244 | synchronized (mainLock) { |
| 245 | if (busy != toolkitThreadBusy) { |
| 246 | if (busy) { |
| 247 | if (blockerThread == null) { |
| 248 | activateBlockerThread(); |
| 249 | } else if (isReadyToShutdown()) { |
| 250 | mainLock.notifyAll(); |
| 251 | timeoutPassed = false; |
| 252 | } |
| 253 | toolkitThreadBusy = busy; |
| 254 | } else { |
| 255 | toolkitThreadBusy = busy; |
| 256 | if (isReadyToShutdown()) { |
| 257 | mainLock.notifyAll(); |
| 258 | timeoutPassed = false; |
| 259 | } |
| 260 | } |
| 261 | } |
| 262 | } |
| 263 | } |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | /** |
| 268 | * Implementation of the Runnable interface. |
| 269 | * Incapsulates the blocker thread functionality. |
| 270 | * |
| 271 | * @see AWTAutoShutdown#isReadyToShutdown |
| 272 | */ |
| 273 | public void run() { |
| 274 | Thread currentThread = Thread.currentThread(); |
| 275 | boolean interrupted = false; |
| 276 | synchronized (mainLock) { |
| 277 | try { |
| 278 | /* Notify that the thread is started. */ |
| 279 | mainLock.notifyAll(); |
| 280 | while (blockerThread == currentThread) { |
| 281 | mainLock.wait(); |
| 282 | timeoutPassed = false; |
| 283 | /* |
| 284 | * This loop is introduced to handle the following case: |
| 285 | * it is possible that while we are waiting for the |
| 286 | * safety timeout to pass AWT state can change to |
| 287 | * not-ready-to-shutdown and back to ready-to-shutdown. |
| 288 | * In this case we have to wait once again. |
| 289 | * NOTE: we shouldn't break into the outer loop |
| 290 | * in this case, since we may never be notified |
| 291 | * in an outer infinite wait at this point. |
| 292 | */ |
| 293 | while (isReadyToShutdown()) { |
| 294 | if (timeoutPassed) { |
| 295 | timeoutPassed = false; |
| 296 | blockerThread = null; |
| 297 | break; |
| 298 | } |
| 299 | timeoutPassed = true; |
| 300 | mainLock.wait(SAFETY_TIMEOUT); |
| 301 | } |
| 302 | } |
| 303 | } catch (InterruptedException e) { |
| 304 | interrupted = true; |
| 305 | } finally { |
| 306 | if (blockerThread == currentThread) { |
| 307 | blockerThread = null; |
| 308 | } |
| 309 | } |
| 310 | } |
| 311 | if (!interrupted) { |
| 312 | AppContext.stopEventDispatchThreads(); |
| 313 | } |
| 314 | } |
| 315 | |
| 316 | static AWTEvent getShutdownEvent() { |
| 317 | return new AWTEvent(getInstance(), 0) {}; |
| 318 | } |
| 319 | |
| 320 | /** |
| 321 | * Creates and starts a new blocker thread. Doesn't return until |
| 322 | * the new blocker thread starts. |
| 323 | */ |
| 324 | private void activateBlockerThread() { |
| 325 | Thread thread = new Thread(this, "AWT-Shutdown"); |
| 326 | thread.setDaemon(false); |
| 327 | blockerThread = thread; |
| 328 | thread.start(); |
| 329 | try { |
| 330 | /* Wait for the blocker thread to start. */ |
| 331 | mainLock.wait(); |
| 332 | } catch (InterruptedException e) { |
| 333 | System.err.println("AWT blocker activation interrupted:"); |
| 334 | e.printStackTrace(); |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | final void registerPeer(final Object target, final Object peer) { |
| 339 | synchronized (activationLock) { |
| 340 | synchronized (mainLock) { |
| 341 | peerMap.put(target, peer); |
| 342 | notifyPeerMapUpdated(); |
| 343 | } |
| 344 | } |
| 345 | } |
| 346 | |
| 347 | final void unregisterPeer(final Object target, final Object peer) { |
| 348 | synchronized (activationLock) { |
| 349 | synchronized (mainLock) { |
| 350 | if (peerMap.get(target) == peer) { |
| 351 | peerMap.remove(target); |
| 352 | notifyPeerMapUpdated(); |
| 353 | } |
| 354 | } |
| 355 | } |
| 356 | } |
| 357 | |
| 358 | final Object getPeer(final Object target) { |
| 359 | synchronized (activationLock) { |
| 360 | synchronized (mainLock) { |
| 361 | return peerMap.get(target); |
| 362 | } |
| 363 | } |
| 364 | } |
| 365 | |
| 366 | final void dumpPeers(final Logger aLog) { |
| 367 | synchronized (activationLock) { |
| 368 | synchronized (mainLock) { |
| 369 | aLog.fine("Mapped peers:"); |
| 370 | for (Object key : peerMap.keySet()) { |
| 371 | aLog.fine(key + "->" + peerMap.get(key)); |
| 372 | } |
| 373 | } |
| 374 | } |
| 375 | } |
| 376 | |
| 377 | } // class AWTAutoShutdown |