1 /*
   2  * Copyright (c) 2000, 2014, 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.security.AccessController;
  29 import java.security.PrivilegedAction;
  30 import java.util.LinkedList;
  31 import sun.awt.AWTAccessor;
  32 import sun.awt.AppContext;
  33 import sun.awt.SunToolkit;
  34 
  35 /**
  36  * A mechanism for ensuring that a series of AWTEvents are executed in a
  37  * precise order, even across multiple AppContexts. The nested events will be
  38  * dispatched in the order in which their wrapping SequencedEvents were
  39  * constructed. The only exception to this rule is if the peer of the target of
  40  * the nested event was destroyed (with a call to Component.removeNotify)
  41  * before the wrapping SequencedEvent was able to be dispatched. In this case,
  42  * the nested event is never dispatched.
  43  *
  44  * @author David Mendenhall
  45  */
  46 class SequencedEvent extends AWTEvent implements ActiveEvent {
  47     /*
  48      * serialVersionUID
  49      */
  50     private static final long serialVersionUID = 547742659238625067L;
  51 
  52     private static final int ID =
  53         java.awt.event.FocusEvent.FOCUS_LAST + 1;
  54     private static final LinkedList<SequencedEvent> list = new LinkedList<>();
  55 
  56     private final AWTEvent nested;
  57     private AppContext appContext;
  58     private boolean disposed;
  59 
  60     private static boolean fxAppThreadIsDispatchThread;
  61     private Thread fxCheckSequenceThread;
  62     static {
  63         AWTAccessor.setSequencedEventAccessor(new AWTAccessor.SequencedEventAccessor() {
  64             public AWTEvent getNested(AWTEvent sequencedEvent) {
  65                 return ((SequencedEvent)sequencedEvent).nested;
  66             }
  67             public boolean isSequencedEvent(AWTEvent event) {
  68                 return event instanceof SequencedEvent;
  69             }
  70 
  71             public AWTEvent create(AWTEvent event) {
  72                 return new SequencedEvent(event);
  73             }
  74         });
  75         AccessController.doPrivileged(new PrivilegedAction<Object>() {
  76             public Object run() {
  77                 fxAppThreadIsDispatchThread =
  78                         "true".equals(System.getProperty("javafx.embed.singleThread"));
  79                 return null;
  80             }
  81         });
  82     }
  83 
  84     /**
  85      * Constructs a new SequencedEvent which will dispatch the specified
  86      * nested event.
  87      *
  88      * @param nested the AWTEvent which this SequencedEvent's dispatch()
  89      *        method will dispatch
  90      */
  91     public SequencedEvent(AWTEvent nested) {
  92         super(nested.getSource(), ID);
  93         this.nested = nested;
  94         // All AWTEvents that are wrapped in SequencedEvents are (at
  95         // least currently) implicitly generated by the system
  96         SunToolkit.setSystemGenerated(nested);
  97 
  98         if (fxAppThreadIsDispatchThread) {
  99             fxCheckSequenceThread = new Thread() {
 100                 @Override
 101                 public void run() {
 102                     while(!isFirstOrDisposed()) {
 103                         try {
 104                             Thread.sleep(100);
 105                         } catch (InterruptedException e) {
 106                             break;
 107                         }
 108                     }
 109                 }
 110             };
 111         }
 112         synchronized (SequencedEvent.class) {
 113             list.add(this);
 114         }
 115     }
 116 
 117     /**
 118      * Dispatches the nested event after all previous nested events have been
 119      * dispatched or disposed. If this method is invoked before all previous nested events
 120      * have been dispatched, then this method blocks until such a point is
 121      * reached.
 122      * While waiting disposes nested events to disposed AppContext
 123      *
 124      * NOTE: Locking protocol.  Since dispose() can get EventQueue lock,
 125      * dispatch() shall never call dispose() while holding the lock on the list,
 126      * as EventQueue lock is held during dispatching.  The locks should be acquired
 127      * in the same order.
 128      */
 129     public final void dispatch() {
 130         try {
 131             appContext = AppContext.getAppContext();
 132 
 133             if (getFirst() != this) {
 134                 if (EventQueue.isDispatchThread()) {
 135                     if (Thread.currentThread() instanceof EventDispatchThread) {
 136                         EventDispatchThread edt = (EventDispatchThread)
 137                                 Thread.currentThread();
 138                         edt.pumpEvents(SentEvent.ID, new Conditional() {
 139                             public boolean evaluate() {
 140                                 return !SequencedEvent.this.isFirstOrDisposed();
 141                             }
 142                         });
 143                     } else {
 144                         if (fxAppThreadIsDispatchThread) {
 145                             fxCheckSequenceThread.start();
 146                             try {
 147                                 // check if event is dispatched or disposed
 148                                 // but since this user app thread is same as
 149                                 // dispatch thread in fx when run with
 150                                 // javafx.embed.singleThread=true
 151                                 // we do not wait infinitely to avoid deadlock
 152                                 // as dispatch will ultimately be done by this thread
 153                                 fxCheckSequenceThread.join(500);
 154                             } catch (InterruptedException e) {
 155                             }
 156                         }
 157                     }
 158                 } else {
 159                     while(!isFirstOrDisposed()) {
 160                         synchronized (SequencedEvent.class) {
 161                             try {
 162                                 SequencedEvent.class.wait(1000);
 163                             } catch (InterruptedException e) {
 164                                 break;
 165                             }
 166                         }
 167                     }
 168                 }
 169             }
 170 
 171             if (!disposed) {
 172                 KeyboardFocusManager.getCurrentKeyboardFocusManager().
 173                     setCurrentSequencedEvent(this);
 174                 Toolkit.getEventQueue().dispatchEvent(nested);
 175             }
 176         } finally {
 177             dispose();
 178         }
 179     }
 180 
 181     /**
 182      * true only if event exists and nested source appContext is disposed.
 183      */
 184     private static final boolean isOwnerAppContextDisposed(SequencedEvent se) {
 185         if (se != null) {
 186             Object target = se.nested.getSource();
 187             if (target instanceof Component) {
 188                 return ((Component)target).appContext.isDisposed();
 189             }
 190         }
 191         return false;
 192     }
 193 
 194     /**
 195      * Sequenced events are dispatched in order, so we cannot dispatch
 196      * until we are the first sequenced event in the queue (i.e. it's our
 197      * turn).  But while we wait for our turn to dispatch, the event
 198      * could have been disposed for a number of reasons.
 199      */
 200     public final boolean isFirstOrDisposed() {
 201         if (disposed) {
 202             return true;
 203         }
 204         // getFirstWithContext can dispose this
 205         return this == getFirstWithContext() || disposed;
 206     }
 207 
 208     private static final synchronized SequencedEvent getFirst() {
 209         return list.getFirst();
 210     }
 211 
 212     /* Disposes all events from disposed AppContext
 213      * return first valid event
 214      */
 215     private static final SequencedEvent getFirstWithContext() {
 216         SequencedEvent first = getFirst();
 217         while(isOwnerAppContextDisposed(first)) {
 218             first.dispose();
 219             first = getFirst();
 220         }
 221         return first;
 222     }
 223 
 224     /**
 225      * Disposes of this instance. This method is invoked once the nested event
 226      * has been dispatched and handled, or when the peer of the target of the
 227      * nested event has been disposed with a call to Component.removeNotify.
 228      *
 229      * NOTE: Locking protocol.  Since SunToolkit.postEvent can get EventQueue lock,
 230      * it shall never be called while holding the lock on the list,
 231      * as EventQueue lock is held during dispatching and dispatch() will get
 232      * lock on the list. The locks should be acquired in the same order.
 233      */
 234     final void dispose() {
 235       synchronized (SequencedEvent.class) {
 236             if (disposed) {
 237                 return;
 238             }
 239             if (KeyboardFocusManager.getCurrentKeyboardFocusManager().
 240                     getCurrentSequencedEvent() == this) {
 241                 KeyboardFocusManager.getCurrentKeyboardFocusManager().
 242                     setCurrentSequencedEvent(null);
 243             }
 244             disposed = true;
 245         }
 246         // Wake myself up
 247         if (appContext != null) {
 248             SunToolkit.postEvent(appContext, new SentEvent());
 249         }
 250 
 251         SequencedEvent next = null;
 252 
 253         synchronized (SequencedEvent.class) {
 254           SequencedEvent.class.notifyAll();
 255 
 256           if (list.getFirst() == this) {
 257               list.removeFirst();
 258 
 259               if (!list.isEmpty()) {
 260                     next = list.getFirst();
 261               }
 262           } else {
 263               list.remove(this);
 264           }
 265       }
 266         // Wake up waiting threads
 267         if (next != null && next.appContext != null) {
 268             SunToolkit.postEvent(next.appContext, new SentEvent());
 269         }
 270     }
 271 }