1 /*
   2  * Copyright (c) 2010, 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 
  69     private static synchronized void initializeTimer() {
  70         if (timer == null) {
  71             timer = new Timer("AWT-WaitDispatchSupport-Timer", true);
  72         }
  73     }
  74 
  75     /**
  76      * Creates a {@code WaitDispatchSupport} instance to
  77      * serve the given event dispatch thread.
  78      *
  79      * @param dispatchThread An event dispatch thread that
  80      *        should not stop dispatching events while waiting
  81      *
  82      * @since 1.7
  83      */
  84     public WaitDispatchSupport(EventDispatchThread dispatchThread) {
  85         this(dispatchThread, null);
  86     }
  87 
  88     /**
  89      * Creates a {@code WaitDispatchSupport} instance to
  90      * serve the given event dispatch thread.
  91      *
  92      * @param dispatchThread An event dispatch thread that
  93      *        should not stop dispatching events while waiting
  94      * @param extCondition A conditional object used to determine
  95      *        if the loop should be terminated
  96      *
  97      * @since 1.7
  98      */
  99     public WaitDispatchSupport(EventDispatchThread dispatchThread,
 100                                Conditional extCond)
 101     {
 102         if (dispatchThread == null) {
 103             throw new IllegalArgumentException("The dispatchThread can not be null");
 104         }
 105 
 106         this.dispatchThread = dispatchThread;
 107         this.extCondition = extCond;
 108         this.condition = new Conditional() {
 109             @Override
 110             public boolean evaluate() {
 111                 if (log.isLoggable(PlatformLogger.FINEST)) {
 112                     log.finest("evaluate(): blockingEDT=" + keepBlockingEDT.get() +
 113                                ", blockingCT=" + keepBlockingCT.get());
 114                 }
 115                 boolean extEvaluate =
 116                     (extCondition != null) ? extCondition.evaluate() : true;
 117                 if (!keepBlockingEDT.get() || !extEvaluate) {
 118                     if (timerTask != null) {
 119                         timerTask.cancel();
 120                         timerTask = null;
 121                     }
 122                     return false;
 123                 }
 124                 return true;
 125             }
 126         };
 127     }
 128 
 129     /**
 130      * Creates a {@code WaitDispatchSupport} instance to
 131      * serve the given event dispatch thread.
 132      * <p>
 133      * The {@link EventFilter} is set on the {@code dispatchThread}
 134      * while waiting. The filter is removed on completion of the
 135      * waiting process.
 136      * <p>
 137      *
 138      *
 139      * @param dispatchThread An event dispatch thread that
 140      *        should not stop dispatching events while waiting
 141      * @param filter {@code EventFilter} to be set
 142      * @param interval A time interval to wait for. Note that
 143      *        when the waiting process takes place on EDT
 144      *        there is no guarantee to stop it in the given time
 145      *
 146      * @since 1.7
 147      */
 148     public WaitDispatchSupport(EventDispatchThread dispatchThread,
 149                                Conditional extCondition,
 150                                EventFilter filter, long interval)
 151     {
 152         this(dispatchThread, extCondition);
 153         this.filter = filter;
 154         if (interval < 0) {
 155             throw new IllegalArgumentException("The interval value must be >= 0");
 156         }
 157         this.interval = interval;
 158         if (interval != 0) {
 159             initializeTimer();
 160         }
 161     }
 162 
 163     /**
 164      * @inheritDoc
 165      */
 166     @Override
 167     public boolean enter() {
 168         log.fine("enter(): blockingEDT=" + keepBlockingEDT.get() +
 169                  ", blockingCT=" + keepBlockingCT.get());
 170 
 171         if (!keepBlockingEDT.compareAndSet(false, true)) {
 172             log.fine("The secondary loop is already running, aborting");
 173             return false;
 174         }
 175 
 176         final Runnable run = new Runnable() {
 177             public void run() {
 178                 log.fine("Starting a new event pump");
 179                 if (filter == null) {
 180                     dispatchThread.pumpEvents(condition);
 181                 } else {
 182                     dispatchThread.pumpEventsForFilter(condition, filter);
 183                 }
 184             }
 185         };
 186 
 187         // We have two mechanisms for blocking: if we're on the
 188         // dispatch thread, start a new event pump; if we're
 189         // on any other thread, call wait() on the treelock
 190 
 191         Thread currentThread = Thread.currentThread();
 192         if (currentThread == dispatchThread) {
 193             log.finest("On dispatch thread: " + dispatchThread);
 194             if (interval != 0) {
 195                 log.finest("scheduling the timer for " + interval + " ms");
 196                 timer.schedule(timerTask = new TimerTask() {
 197                     @Override
 198                     public void run() {
 199                         if (keepBlockingEDT.compareAndSet(true, false)) {
 200                             wakeupEDT();
 201                         }
 202                     }
 203                 }, interval);
 204             }
 205             // Dispose SequencedEvent we are dispatching on the the current
 206             // AppContext, to prevent us from hang - see 4531693 for details
 207             SequencedEvent currentSE = KeyboardFocusManager.
 208                 getCurrentKeyboardFocusManager().getCurrentSequencedEvent();
 209             if (currentSE != null) {
 210                 log.fine("Dispose current SequencedEvent: " + currentSE);
 211                 currentSE.dispose();
 212             }
 213             // In case the exit() method is called before starting
 214             // new event pump it will post the waking event to EDT.
 215             // The event will be handled after the the new event pump
 216             // starts. Thus, the enter() method will not hang.
 217             //
 218             // Event pump should be privileged. See 6300270.
 219             AccessController.doPrivileged(new PrivilegedAction() {
 220                 public Object run() {
 221                     run.run();
 222                     return null;
 223                 }
 224             });
 225         } else {
 226             log.finest("On non-dispatch thread: " + currentThread);
 227             synchronized (getTreeLock()) {
 228                 if (filter != null) {
 229                     dispatchThread.addEventFilter(filter);
 230                 }
 231                 try {
 232                     EventQueue eq = dispatchThread.getEventQueue();
 233                     eq.postEvent(new PeerEvent(this, run, PeerEvent.PRIORITY_EVENT));
 234                     keepBlockingCT.set(true);
 235                     if (interval > 0) {
 236                         long currTime = System.currentTimeMillis();
 237                         while (keepBlockingCT.get() &&
 238                                ((extCondition != null) ? extCondition.evaluate() : true) &&
 239                                (currTime + interval > System.currentTimeMillis()))
 240                         {
 241                             getTreeLock().wait(interval);
 242                         }
 243                     } else {
 244                         while (keepBlockingCT.get() &&
 245                                ((extCondition != null) ? extCondition.evaluate() : true))
 246                         {
 247                             getTreeLock().wait();
 248                         }
 249                     }
 250                     log.fine("waitDone " + keepBlockingEDT.get() + " " + keepBlockingCT.get());
 251                 } catch (InterruptedException e) {
 252                     log.fine("Exception caught while waiting: " + e);
 253                 } finally {
 254                     if (filter != null) {
 255                         dispatchThread.removeEventFilter(filter);
 256                     }
 257                 }
 258                 // If the waiting process has been stopped because of the
 259                 // time interval passed or an exception occurred, the state
 260                 // should be changed
 261                 keepBlockingEDT.set(false);
 262                 keepBlockingCT.set(false);
 263             }
 264         }
 265 
 266         return true;
 267     }
 268 
 269     /**
 270      * @inheritDoc
 271      */
 272     public boolean exit() {
 273         log.fine("exit(): blockingEDT=" + keepBlockingEDT.get() +
 274                  ", blockingCT=" + keepBlockingCT.get());
 275         if (keepBlockingEDT.compareAndSet(true, false)) {
 276             wakeupEDT();
 277             return true;
 278         }
 279         return false;
 280     }
 281 
 282     private final static Object getTreeLock() {
 283         return Component.LOCK;
 284     }
 285 
 286     private final Runnable wakingRunnable = new Runnable() {
 287         public void run() {
 288             log.fine("Wake up EDT");
 289             synchronized (getTreeLock()) {
 290                 keepBlockingCT.set(false);
 291                 getTreeLock().notifyAll();
 292             }
 293             log.fine("Wake up EDT done");
 294         }
 295     };
 296 
 297     private void wakeupEDT() {
 298         log.finest("wakeupEDT(): EDT == " + dispatchThread);
 299         EventQueue eq = dispatchThread.getEventQueue();
 300         eq.postEvent(new PeerEvent(this, wakingRunnable, PeerEvent.PRIORITY_EVENT));
 301     }
 302 }