| /* | |
| * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package java.awt; | |
| import java.io.Serial; | |
| import java.util.LinkedList; | |
| import sun.awt.AWTAccessor; | |
| import sun.awt.AppContext; | |
| import sun.awt.SunToolkit; | |
| /** | |
| * A mechanism for ensuring that a series of AWTEvents are executed in a | |
| * precise order, even across multiple AppContexts. The nested events will be | |
| * dispatched in the order in which their wrapping SequencedEvents were | |
| * constructed. The only exception to this rule is if the peer of the target of | |
| * the nested event was destroyed (with a call to Component.removeNotify) | |
| * before the wrapping SequencedEvent was able to be dispatched. In this case, | |
| * the nested event is never dispatched. | |
| * | |
| * @author David Mendenhall | |
| */ | |
| class SequencedEvent extends AWTEvent implements ActiveEvent { | |
| /** | |
| * Use serialVersionUID from JDK 1.6 for interoperability. | |
| */ | |
| @Serial | |
| private static final long serialVersionUID = 547742659238625067L; | |
| private static final int ID = | |
| java.awt.event.FocusEvent.FOCUS_LAST + 1; | |
| private static final LinkedList<SequencedEvent> list = new LinkedList<>(); | |
| private final AWTEvent nested; | |
| @SuppressWarnings("serial") // Not statically typed as Serializable | |
| private AppContext appContext; | |
| private boolean disposed; | |
| private final LinkedList<AWTEvent> pendingEvents = new LinkedList<>(); | |
| private static boolean fxAppThreadIsDispatchThread; | |
| @SuppressWarnings("serial") // Not statically typed as Serializable | |
| private Thread fxCheckSequenceThread; | |
| 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); | |
| } | |
| }); | |
| fxAppThreadIsDispatchThread = "true".equals(System.getProperty("javafx.embed.singleThread")); | |
| } | |
| private static final class SequencedEventsFilter implements EventFilter { | |
| private final SequencedEvent currentSequencedEvent; | |
| private SequencedEventsFilter(SequencedEvent currentSequencedEvent) { | |
| this.currentSequencedEvent = currentSequencedEvent; | |
| } | |
| @Override | |
| public FilterAction acceptEvent(AWTEvent ev) { | |
| if (ev.getID() == ID) { | |
| // Move forward dispatching only if the event is previous | |
| // in SequencedEvent.list. Otherwise, hold it for reposting later. | |
| synchronized (SequencedEvent.class) { | |
| for (SequencedEvent iev : list) { | |
| if (iev.equals(currentSequencedEvent)) { | |
| break; | |
| } else if (iev.equals(ev)) { | |
| return FilterAction.ACCEPT; | |
| } | |
| } | |
| } | |
| } else if (ev.getID() == SentEvent.ID) { | |
| return FilterAction.ACCEPT; | |
| } | |
| currentSequencedEvent.pendingEvents.add(ev); | |
| return FilterAction.REJECT; | |
| } | |
| } | |
| /** | |
| * Constructs a new SequencedEvent which will dispatch the specified | |
| * nested event. | |
| * | |
| * @param nested the AWTEvent which this SequencedEvent's dispatch() | |
| * method will dispatch | |
| */ | |
| public SequencedEvent(AWTEvent nested) { | |
| super(nested.getSource(), ID); | |
| this.nested = nested; | |
| // All AWTEvents that are wrapped in SequencedEvents are (at | |
| // least currently) implicitly generated by the system | |
| SunToolkit.setSystemGenerated(nested); | |
| if (fxAppThreadIsDispatchThread) { | |
| fxCheckSequenceThread = new Thread() { | |
| @Override | |
| public void run() { | |
| while(!isFirstOrDisposed()) { | |
| try { | |
| Thread.sleep(100); | |
| } catch (InterruptedException e) { | |
| break; | |
| } | |
| } | |
| } | |
| }; | |
| } | |
| synchronized (SequencedEvent.class) { | |
| list.add(this); | |
| } | |
| } | |
| /** | |
| * Dispatches the nested event after all previous nested events have been | |
| * dispatched or disposed. If this method is invoked before all previous nested events | |
| * have been dispatched, then this method blocks until such a point is | |
| * reached. | |
| * While waiting disposes nested events to disposed AppContext | |
| * | |
| * NOTE: Locking protocol. Since dispose() can get EventQueue lock, | |
| * dispatch() shall never call dispose() while holding the lock on the list, | |
| * as EventQueue lock is held during dispatching. The locks should be acquired | |
| * in the same order. | |
| */ | |
| public final void dispatch() { | |
| try { | |
| appContext = AppContext.getAppContext(); | |
| if (getFirst() != this) { | |
| if (EventQueue.isDispatchThread()) { | |
| if (Thread.currentThread() instanceof EventDispatchThread) { | |
| EventDispatchThread edt = (EventDispatchThread) | |
| Thread.currentThread(); | |
| edt.pumpEventsForFilter(() -> !SequencedEvent.this.isFirstOrDisposed(), | |
| new SequencedEventsFilter(this)); | |
| } else { | |
| if (fxAppThreadIsDispatchThread) { | |
| fxCheckSequenceThread.start(); | |
| try { | |
| // check if event is dispatched or disposed | |
| // but since this user app thread is same as | |
| // dispatch thread in fx when run with | |
| // javafx.embed.singleThread=true | |
| // we do not wait infinitely to avoid deadlock | |
| // as dispatch will ultimately be done by this thread | |
| fxCheckSequenceThread.join(500); | |
| } catch (InterruptedException e) { | |
| } | |
| } | |
| } | |
| } else { | |
| while(!isFirstOrDisposed()) { | |
| synchronized (SequencedEvent.class) { | |
| try { | |
| SequencedEvent.class.wait(1000); | |
| } catch (InterruptedException e) { | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (!disposed) { | |
| KeyboardFocusManager.getCurrentKeyboardFocusManager(). | |
| setCurrentSequencedEvent(this); | |
| Toolkit.getEventQueue().dispatchEvent(nested); | |
| } | |
| } finally { | |
| dispose(); | |
| } | |
| } | |
| /** | |
| * true only if event exists and nested source appContext is disposed. | |
| */ | |
| private static final boolean isOwnerAppContextDisposed(SequencedEvent se) { | |
| if (se != null) { | |
| Object target = se.nested.getSource(); | |
| if (target instanceof Component) { | |
| return ((Component)target).appContext.isDisposed(); | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Sequenced events are dispatched in order, so we cannot dispatch | |
| * until we are the first sequenced event in the queue (i.e. it's our | |
| * turn). But while we wait for our turn to dispatch, the event | |
| * could have been disposed for a number of reasons. | |
| */ | |
| public final boolean isFirstOrDisposed() { | |
| if (disposed) { | |
| return true; | |
| } | |
| // getFirstWithContext can dispose this | |
| return this == getFirstWithContext() || disposed; | |
| } | |
| private static final synchronized SequencedEvent getFirst() { | |
| return list.getFirst(); | |
| } | |
| /* Disposes all events from disposed AppContext | |
| * return first valid event | |
| */ | |
| private static final SequencedEvent getFirstWithContext() { | |
| SequencedEvent first = getFirst(); | |
| while(isOwnerAppContextDisposed(first)) { | |
| first.dispose(); | |
| first = getFirst(); | |
| } | |
| return first; | |
| } | |
| /** | |
| * Disposes of this instance. This method is invoked once the nested event | |
| * has been dispatched and handled, or when the peer of the target of the | |
| * nested event has been disposed with a call to Component.removeNotify. | |
| * | |
| * NOTE: Locking protocol. Since SunToolkit.postEvent can get EventQueue lock, | |
| * it shall never be called while holding the lock on the list, | |
| * as EventQueue lock is held during dispatching and dispatch() will get | |
| * lock on the list. The locks should be acquired in the same order. | |
| */ | |
| final void dispose() { | |
| synchronized (SequencedEvent.class) { | |
| if (disposed) { | |
| return; | |
| } | |
| if (KeyboardFocusManager.getCurrentKeyboardFocusManager(). | |
| getCurrentSequencedEvent() == this) { | |
| KeyboardFocusManager.getCurrentKeyboardFocusManager(). | |
| setCurrentSequencedEvent(null); | |
| } | |
| disposed = true; | |
| } | |
| SequencedEvent next = null; | |
| synchronized (SequencedEvent.class) { | |
| SequencedEvent.class.notifyAll(); | |
| if (list.getFirst() == this) { | |
| list.removeFirst(); | |
| if (!list.isEmpty()) { | |
| next = list.getFirst(); | |
| } | |
| } else { | |
| list.remove(this); | |
| } | |
| } | |
| // Wake up waiting threads | |
| if (next != null && next.appContext != null) { | |
| SunToolkit.postEvent(next.appContext, new SentEvent()); | |
| } | |
| for(AWTEvent e : pendingEvents) { | |
| SunToolkit.postEvent(appContext, e); | |
| } | |
| } | |
| } |