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