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 }