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 { 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 }); 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; 88 private SequencedEventsFilter(SequencedEvent currentSequencedEvent) { 89 this.currentSequencedEvent = currentSequencedEvent; 90 } 91 @Override 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 */ 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 */ 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 */ 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 */ 228 public final boolean isFirstOrDisposed() { 229 if (disposed) { 230 return true; 231 } 232 // getFirstWithContext can dispose this 233 return this == getFirstWithContext() || disposed; 234 } 235 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 */ 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 */ 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 }