1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.awt;
  27 
  28 import java.util.Timer;
  29 import java.util.TimerTask;
  30 import java.util.concurrent.atomic.AtomicBoolean;
  31 
  32 import java.security.PrivilegedAction;
  33 import java.security.AccessController;
  34 
  35 import sun.awt.PeerEvent;
  36 
  37 import sun.util.logging.PlatformLogger;
  38 
  39 /**
  40  * This utility class is used to suspend execution on a thread
  41  * while still allowing {@code EventDispatchThread} to dispatch events.
  42  * The API methods of the class are thread-safe.
  43  *
  44  * @author Anton Tarasov, Artem Ananiev
  45  *
  46  * @since 1.7
  47  */
  48 class WaitDispatchSupport implements SecondaryLoop {
  49 
  50     private final static PlatformLogger log =
  51         PlatformLogger.getLogger("java.awt.event.WaitDispatchSupport");
  52 
  53     private EventDispatchThread dispatchThread;
  54     private EventFilter filter;
  55 
  56     private volatile Conditional extCondition;
  57     private volatile Conditional condition;
  58 
  59     private long interval;
  60     // Use a shared daemon timer to serve all the WaitDispatchSupports
  61     private static Timer timer;
  62     // When this WDS expires, we cancel the timer task leaving the
  63     // shared timer up and running
  64     private TimerTask timerTask;
  65 
  66     private AtomicBoolean keepBlockingEDT = new AtomicBoolean(false);
  67     private AtomicBoolean keepBlockingCT = new AtomicBoolean(false);
  68     private AtomicBoolean afterExit = new AtomicBoolean(false);
  69 
  70     private static synchronized void initializeTimer() {
  71         if (timer == null) {
  72             timer = new Timer("AWT-WaitDispatchSupport-Timer", true);
  73         }
  74     }
  75 
  76     /**
  77      * Creates a {@code WaitDispatchSupport} instance to
  78      * serve the given event dispatch thread.
  79      *
  80      * @param dispatchThread An event dispatch thread that
  81      *        should not stop dispatching events while waiting
  82      *
  83      * @since 1.7
  84      */
  85     public WaitDispatchSupport(EventDispatchThread dispatchThread) {
  86         this(dispatchThread, null);
  87     }
  88 
  89     /**
  90      * Creates a {@code WaitDispatchSupport} instance to
  91      * serve the given event dispatch thread.
  92      *
  93      * @param dispatchThread An event dispatch thread that
  94      *        should not stop dispatching events while waiting
  95      * @param extCond A conditional object used to determine
  96      *        if the loop should be terminated
  97      *
  98      * @since 1.7
  99      */
 100     public WaitDispatchSupport(EventDispatchThread dispatchThread,
 101                                Conditional extCond)
 102     {
 103         if (dispatchThread == null) {
 104             throw new IllegalArgumentException("The dispatchThread can not be null");
 105         }
 106 
 107         this.dispatchThread = dispatchThread;
 108         this.extCondition = extCond;
 109         this.condition = new Conditional() {
 110             @Override
 111             public boolean evaluate() {
 112                 if (log.isLoggable(PlatformLogger.Level.FINEST)) {
 113                     log.finest("evaluate(): blockingEDT=" + keepBlockingEDT.get() +
 114                                ", blockingCT=" + keepBlockingCT.get());
 115                 }
 116                 boolean extEvaluate =
 117                     (extCondition != null) ? extCondition.evaluate() : true;
 118                 if (!keepBlockingEDT.get() || !extEvaluate || afterExit.get()) {
 119                     if (timerTask != null) {
 120                         timerTask.cancel();
 121                         timerTask = null;
 122                     }
 123                     return false;
 124                 }
 125                 return true;
 126             }
 127         };
 128     }
 129 
 130     /**
 131      * Creates a {@code WaitDispatchSupport} instance to
 132      * serve the given event dispatch thread.
 133      * <p>
 134      * The {@link EventFilter} is set on the {@code dispatchThread}
 135      * while waiting. The filter is removed on completion of the
 136      * waiting process.
 137      * <p>
 138      *
 139      *
 140      * @param dispatchThread An event dispatch thread that
 141      *        should not stop dispatching events while waiting
 142      * @param filter {@code EventFilter} to be set
 143      * @param interval A time interval to wait for. Note that
 144      *        when the waiting process takes place on EDT
 145      *        there is no guarantee to stop it in the given time
 146      *
 147      * @since 1.7
 148      */
 149     public WaitDispatchSupport(EventDispatchThread dispatchThread,
 150                                Conditional extCondition,
 151                                EventFilter filter, long interval)
 152     {
 153         this(dispatchThread, extCondition);
 154         this.filter = filter;
 155         if (interval < 0) {
 156             throw new IllegalArgumentException("The interval value must be >= 0");
 157         }
 158         this.interval = interval;
 159         if (interval != 0) {
 160             initializeTimer();
 161         }
 162     }
 163 
 164     /**
 165      * {@inheritDoc}
 166      */
 167     @Override
 168     public boolean enter() {
 169         if (log.isLoggable(PlatformLogger.Level.FINE)) {
 170             log.fine("enter(): blockingEDT=" + keepBlockingEDT.get() +
 171                      ", blockingCT=" + keepBlockingCT.get());
 172         }
 173 
 174         if (!keepBlockingEDT.compareAndSet(false, true)) {
 175             log.fine("The secondary loop is already running, aborting");
 176             return false;
 177         }
 178         try {
 179             if (afterExit.get()) {
 180                 log.fine("Exit was called already, aborting");
 181                 return false;
 182             }
 183 
 184             final Runnable run = new Runnable() {
 185                 public void run() {
 186                     log.fine("Starting a new event pump");
 187                     if (filter == null) {
 188                         dispatchThread.pumpEvents(condition);
 189                     } else {
 190                         dispatchThread.pumpEventsForFilter(condition, filter);
 191                     }
 192                 }
 193             };
 194 
 195             // We have two mechanisms for blocking: if we're on the
 196             // dispatch thread, start a new event pump; if we're
 197             // on any other thread, call wait() on the treelock
 198 
 199             Thread currentThread = Thread.currentThread();
 200             if (currentThread == dispatchThread) {
 201                 if (log.isLoggable(PlatformLogger.Level.FINEST)) {
 202                     log.finest("On dispatch thread: " + dispatchThread);
 203                 }
 204                 if (interval != 0) {
 205                     if (log.isLoggable(PlatformLogger.Level.FINEST)) {
 206                         log.finest("scheduling the timer for " + interval + " ms");
 207                     }
 208                     timer.schedule(timerTask = new TimerTask() {
 209                         @Override
 210                         public void run() {
 211                             if (keepBlockingEDT.compareAndSet(true, false)) {
 212                                 wakeupEDT();
 213                             }
 214                         }
 215                     }, interval);
 216                 }
 217                 // Dispose SequencedEvent we are dispatching on the current
 218                 // AppContext, to prevent us from hang - see 4531693 for details
 219                 SequencedEvent currentSE = KeyboardFocusManager.
 220                         getCurrentKeyboardFocusManager().getCurrentSequencedEvent();
 221                 if (currentSE != null) {
 222                     if (log.isLoggable(PlatformLogger.Level.FINE)) {
 223                         log.fine("Dispose current SequencedEvent: " + currentSE);
 224                     }
 225                     currentSE.dispose();
 226                 }
 227                 // In case the exit() method is called before starting
 228                 // new event pump it will post the waking event to EDT.
 229                 // The event will be handled after the new event pump
 230                 // starts. Thus, the enter() method will not hang.
 231                 //
 232                 // Event pump should be privileged. See 6300270.
 233                 AccessController.doPrivileged(new PrivilegedAction<Void>() {
 234                     public Void run() {
 235                         run.run();
 236                         return null;
 237                     }
 238                 });
 239             } else {
 240                 if (log.isLoggable(PlatformLogger.Level.FINEST)) {
 241                     log.finest("On non-dispatch thread: " + currentThread);
 242                 }
 243                 keepBlockingCT.set(true);
 244                 synchronized (getTreeLock()) {
 245                     if (afterExit.get()) return false;
 246                     if (filter != null) {
 247                         dispatchThread.addEventFilter(filter);
 248                     }
 249                     try {
 250                         EventQueue eq = dispatchThread.getEventQueue();
 251                         eq.postEvent(new PeerEvent(this, run, PeerEvent.PRIORITY_EVENT));
 252                         if (interval > 0) {
 253                             long currTime = System.currentTimeMillis();
 254                             while (keepBlockingCT.get() &&
 255                                     ((extCondition != null) ? extCondition.evaluate() : true) &&
 256                                     (currTime + interval > System.currentTimeMillis()))
 257                             {
 258                                 getTreeLock().wait(interval);
 259                             }
 260                         } else {
 261                             while (keepBlockingCT.get() &&
 262                                     ((extCondition != null) ? extCondition.evaluate() : true))
 263                             {
 264                                 getTreeLock().wait();
 265                             }
 266                         }
 267                         if (log.isLoggable(PlatformLogger.Level.FINE)) {
 268                             log.fine("waitDone " + keepBlockingEDT.get() + " " + keepBlockingCT.get());
 269                         }
 270                     } catch (InterruptedException e) {
 271                         if (log.isLoggable(PlatformLogger.Level.FINE)) {
 272                             log.fine("Exception caught while waiting: " + e);
 273                         }
 274                     } finally {
 275                         if (filter != null) {
 276                             dispatchThread.removeEventFilter(filter);
 277                         }
 278                     }
 279                 }
 280             }
 281             return true;
 282         }
 283         finally {
 284             keepBlockingEDT.set(false);
 285             keepBlockingCT.set(false);
 286             afterExit.set(false);
 287         }
 288     }
 289 
 290     /**
 291      * {@inheritDoc}
 292      */
 293     public boolean exit() {
 294         if (log.isLoggable(PlatformLogger.Level.FINE)) {
 295             log.fine("exit(): blockingEDT=" + keepBlockingEDT.get() +
 296                      ", blockingCT=" + keepBlockingCT.get());
 297         }
 298         afterExit.set(true);
 299         if (keepBlockingEDT.getAndSet(false)) {
 300             wakeupEDT();
 301             return true;
 302         }
 303         return false;
 304     }
 305 
 306     private final static Object getTreeLock() {
 307         return Component.LOCK;
 308     }
 309 
 310     private final Runnable wakingRunnable = new Runnable() {
 311         public void run() {
 312             log.fine("Wake up EDT");
 313             synchronized (getTreeLock()) {
 314                 keepBlockingCT.set(false);
 315                 getTreeLock().notifyAll();
 316             }
 317             log.fine("Wake up EDT done");
 318         }
 319     };
 320 
 321     private void wakeupEDT() {
 322         if (log.isLoggable(PlatformLogger.Level.FINEST)) {
 323             log.finest("wakeupEDT(): EDT == " + dispatchThread);
 324         }
 325         EventQueue eq = dispatchThread.getEventQueue();
 326         eq.postEvent(new PeerEvent(this, wakingRunnable, PeerEvent.PRIORITY_EVENT));
 327     }
 328 }