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