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 {
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); } })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         });
AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { fxAppThreadIsDispatchThread = R.equals(System.getProperty(R)); return null; } })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;
SequencedEventsFilter(SequencedEvent currentSequencedEvent)90         private SequencedEventsFilter(SequencedEvent currentSequencedEvent) {
91             this.currentSequencedEvent = currentSequencedEvent;
92         }
93         @Override
acceptEvent(AWTEvent ev)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      */
SequencedEvent(AWTEvent nested)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      */
dispatch()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      */
isOwnerAppContextDisposed(SequencedEvent se)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      */
isFirstOrDisposed()230     public final boolean isFirstOrDisposed() {
231         if (disposed) {
232             return true;
233         }
234         // getFirstWithContext can dispose this
235         return this == getFirstWithContext() || disposed;
236     }
237 
getFirst()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      */
getFirstWithContext()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      */
dispose()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 }
302