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