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