提交 b35e03fd 编写于 作者: A art

6949936: Provide API for running nested events loops, similar to what modal dialogs do

Reviewed-by: ant, anthony
上级 4a65aa9b
......@@ -277,10 +277,8 @@ public class Dialog extends Window {
*/
String title;
private transient volatile boolean keepBlockingEDT = false;
private transient volatile boolean keepBlockingCT = false;
private transient ModalEventFilter modalFilter;
private transient volatile SecondaryLoop secondaryLoop;
/*
* Indicates that this dialog is being hidden. This flag is set to true at
......@@ -1005,12 +1003,6 @@ public class Dialog extends Window {
super.setVisible(b);
}
/**
* Stores the app context on which event dispatch thread the dialog
* is being shown. Initialized in show(), used in hideAndDisposeHandler()
*/
transient private AppContext showAppContext;
/**
* Makes the {@code Dialog} visible. If the dialog and/or its owner
* are not yet displayable, both are made displayable. The
......@@ -1037,39 +1029,18 @@ public class Dialog extends Window {
if (!isModal()) {
conditionalShow(null, null);
} else {
// Set this variable before calling conditionalShow(). That
// way, if the Dialog is hidden right after being shown, we
// won't mistakenly block this thread.
keepBlockingEDT = true;
keepBlockingCT = true;
// Store the app context on which this dialog is being shown.
// Event dispatch thread of this app context will be sleeping until
// we wake it by any event from hideAndDisposeHandler().
showAppContext = AppContext.getAppContext();
AppContext showAppContext = AppContext.getAppContext();
AtomicLong time = new AtomicLong();
Component predictedFocusOwner = null;
try {
predictedFocusOwner = getMostRecentFocusOwner();
if (conditionalShow(predictedFocusOwner, time)) {
// We have two mechanisms for blocking: 1. If we're on the
// EventDispatchThread, start a new event pump. 2. If we're
// on any other thread, call wait() on the treelock.
modalFilter = ModalEventFilter.createFilterForDialog(this);
final Runnable pumpEventsForFilter = new Runnable() {
public void run() {
EventDispatchThread dispatchThread =
(EventDispatchThread)Thread.currentThread();
dispatchThread.pumpEventsForFilter(new Conditional() {
public boolean evaluate() {
synchronized (getTreeLock()) {
return keepBlockingEDT && windowClosingException == null;
}
}
}, modalFilter);
Conditional cond = new Conditional() {
@Override
public boolean evaluate() {
return windowClosingException == null;
}
};
......@@ -1096,44 +1067,10 @@ public class Dialog extends Window {
modalityPushed();
try {
if (EventQueue.isDispatchThread()) {
/*
* dispose SequencedEvent we are dispatching on current
* AppContext, to prevent us from hang.
*
*/
// BugId 4531693 (son@sparc.spb.su)
SequencedEvent currentSequencedEvent = KeyboardFocusManager.
getCurrentKeyboardFocusManager().getCurrentSequencedEvent();
if (currentSequencedEvent != null) {
currentSequencedEvent.dispose();
}
/*
* Event processing is done inside doPrivileged block so that
* it wouldn't matter even if user code is on the stack
* Fix for BugId 6300270
*/
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
pumpEventsForFilter.run();
return null;
}
});
} else {
synchronized (getTreeLock()) {
Toolkit.getEventQueue().postEvent(new PeerEvent(this,
pumpEventsForFilter,
PeerEvent.PRIORITY_EVENT));
while (keepBlockingCT && windowClosingException == null) {
try {
getTreeLock().wait();
} catch (InterruptedException e) {
break;
}
}
}
EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
secondaryLoop = eventQueue.createSecondaryLoop(cond, modalFilter, 5000);
if (!secondaryLoop.enter()) {
secondaryLoop = null;
}
} finally {
modalityPopped();
......@@ -1194,18 +1131,11 @@ public class Dialog extends Window {
windowClosingException = null;
}
}
final class WakingRunnable implements Runnable {
public void run() {
synchronized (getTreeLock()) {
keepBlockingCT = false;
getTreeLock().notifyAll();
}
}
}
private void hideAndDisposePreHandler() {
isInHide = true;
synchronized (getTreeLock()) {
if (keepBlockingEDT) {
if (secondaryLoop != null) {
modalHide();
// dialog can be shown and then disposed before its
// modal filter is created
......@@ -1217,20 +1147,9 @@ public class Dialog extends Window {
}
}
private void hideAndDisposeHandler() {
synchronized (getTreeLock()) {
if (keepBlockingEDT) {
keepBlockingEDT = false;
PeerEvent wakingEvent = new PeerEvent(getToolkit(), new WakingRunnable(), PeerEvent.PRIORITY_EVENT);
AppContext curAppContext = AppContext.getAppContext();
if (showAppContext != curAppContext) {
// Wake up event dispatch thread on which the dialog was
// initially shown
SunToolkit.postEvent(showAppContext, wakingEvent);
showAppContext = null;
} else {
Toolkit.getEventQueue().postEvent(wakingEvent);
}
}
if (secondaryLoop != null) {
secondaryLoop.exit();
secondaryLoop = null;
}
isInHide = false;
}
......
......@@ -113,8 +113,7 @@ class EventDispatchThread extends Thread {
pumpEventsForHierarchy(id, cond, null);
}
void pumpEventsForHierarchy(int id, Conditional cond, Component modalComponent)
{
void pumpEventsForHierarchy(int id, Conditional cond, Component modalComponent) {
pumpEventsForFilter(id, cond, new HierarchyEventFilter(modalComponent));
}
......@@ -124,6 +123,7 @@ class EventDispatchThread extends Thread {
void pumpEventsForFilter(int id, Conditional cond, EventFilter filter) {
addEventFilter(filter);
doDispatch = true;
while (doDispatch && cond.evaluate()) {
if (isInterrupted() || !pumpOneEventForFilters(id)) {
doDispatch = false;
......@@ -133,6 +133,7 @@ class EventDispatchThread extends Thread {
}
void addEventFilter(EventFilter filter) {
eventLog.finest("adding the event filter: " + filter);
synchronized (eventFilters) {
if (!eventFilters.contains(filter)) {
if (filter instanceof ModalEventFilter) {
......@@ -156,6 +157,7 @@ class EventDispatchThread extends Thread {
}
void removeEventFilter(EventFilter filter) {
eventLog.finest("removing the event filter: " + filter);
synchronized (eventFilters) {
eventFilters.remove(filter);
}
......
......@@ -883,6 +883,41 @@ public class EventQueue {
}
}
/**
* Creates a new {@code secondary loop} associated with this
* event queue. Use the {@link SecondaryLoop#enter} and
* {@link SecondaryLoop#exit} methods to start and stop the
* event loop and dispatch the events from this queue.
*
* @return secondaryLoop A new secondary loop object, which can
* be used to launch a new nested event
* loop and dispatch events from this queue
*
* @see SecondaryLoop#enter
* @see SecondaryLoop#exit
*
* @since 1.7
*/
public SecondaryLoop createSecondaryLoop() {
return createSecondaryLoop(null, null, 0);
}
SecondaryLoop createSecondaryLoop(Conditional cond, EventFilter filter, long interval) {
pushPopLock.lock();
try {
if (nextQueue != null) {
// Forward the request to the top of EventQueue stack
return nextQueue.createSecondaryLoop(cond, filter, interval);
}
if (dispatchThread == null) {
initDispatchThread();
}
return new WaitDispatchSupport(dispatchThread, cond, filter, interval);
} finally {
pushPopLock.unlock();
}
}
/**
* Returns true if the calling thread is
* {@link Toolkit#getSystemEventQueue the current AWT EventQueue}'s
......
/*
* Copyright (c) 2010, 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;
/**
* A helper interface to run the nested event loop.
* <p>
* Objects that implement this interface are created with the
* {@link EventQueue#createSecondaryLoop} method. The interface
* provides two methods, {@link enter} and {@link exit},
* which can be used to start and stop the event loop.
* <p>
* When the {@link enter} method is called, the current
* thread is blocked until the loop is terminated by the
* {@link exit} method. Also, a new event loop is started
* on the event dispatch thread, which may or may not be
* the current thread. The loop can be terminated on any
* thread by calling its {@link exit} method. After the
* loop is terminated, the {@code SecondaryLoop} object can
* be reused to run a new nested event loop.
* <p>
* A typical use case of applying this interface is AWT
* and Swing modal dialogs. When a modal dialog is shown on
* the event dispatch thread, it enters a new secondary loop.
* Later, when the dialog is hidden or disposed, it exits
* the loop, and the thread continues its execution.
* <p>
* The following example illustrates a simple use case of
* secondary loops:
*
* <pre>
* SecondaryLoop loop;
*
* JButton jButton = new JButton("Button");
* jButton.addActionListener(new ActionListener() {
* {@code @Override}
* public void actionPerformed(ActionEvent e) {
* Toolkit tk = Toolkit.getDefaultToolkit();
* EventQueue eq = tk.getSystemEventQueue();
* loop = eq.createSecondaryLoop();
*
* // Spawn a new thread to do the work
* Thread worker = new WorkerThread();
* worker.start();
*
* // Enter the loop to block the current event
* // handler, but leave UI responsive
* if (!loop.enter()) {
* // Report an error
* }
* }
* });
*
* class WorkerThread extends Thread {
* {@code @Override}
* public void run() {
* // Perform calculations
* doSomethingUseful();
*
* // Exit the loop
* loop.exit();
* }
* }
* </pre>
*
* @see Dialog#show
* @see EventQueue#createSecondaryLoop
* @see Toolkit#getSystemEventQueue
*
* @author Anton Tarasov, Artem Ananiev
*
* @since 1.7
*/
public interface SecondaryLoop {
/**
* Blocks the execution of the current thread and enters a new
* secondary event loop on the event dispatch thread.
* <p>
* This method can be called by any thread including the event
* dispatch thread. This thread will be blocked until the {@link
* exit} method is called or the loop is terminated. A new
* secondary loop will be created on the event dispatch thread
* for dispatching events in either case.
* <p>
* This method can only start one new event loop at a time per
* object. If a secondary event loop has already been started
* by this object and is currently still running, this method
* returns {@code false} to indicate that it was not successful
* in starting a new event loop. Otherwise, this method blocks
* the calling thread and later returns {@code true} when the
* new event loop is terminated. At such time, this object can
* again be used to start another new event loop.
*
* @return {@code true} after termination of the secondary loop,
* if the secondary loop was started by this call,
* {@code false} otherwise
*/
public boolean enter();
/**
* Unblocks the execution of the thread blocked by the {@link
* enter} method and exits the secondary loop.
* <p>
* This method resumes the thread that called the {@link enter}
* method and exits the secondary loop that was created when
* the {@link enter} method was invoked.
* <p>
* Note that if any other secondary loop is started while this
* loop is running, the blocked thread will not resume execution
* until the nested loop is terminated.
* <p>
* If this secondary loop has not been started with the {@link
* enter} method, or this secondary loop has already finished
* with the {@link exit} method, this method returns {@code
* false}, otherwise {@code true} is returned.
*
* @return {@code true} if this loop was previously started and
* has not yet been finished with the {@link exit} method,
* {@code false} otherwise
*/
public boolean exit();
}
/*
* Copyright (c) 2010, 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.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
import java.security.PrivilegedAction;
import java.security.AccessController;
import sun.awt.PeerEvent;
import sun.util.logging.PlatformLogger;
/**
* This utility class is used to suspend execution on a thread
* while still allowing {@code EventDispatchThread} to dispatch events.
* The API methods of the class are thread-safe.
*
* @author Anton Tarasov, Artem Ananiev
*
* @since 1.7
*/
class WaitDispatchSupport implements SecondaryLoop {
private final static PlatformLogger log =
PlatformLogger.getLogger("java.awt.event.WaitDispatchSupport");
private EventDispatchThread dispatchThread;
private EventFilter filter;
private volatile Conditional extCondition;
private volatile Conditional condition;
private long interval;
// Use a shared daemon timer to serve all the WaitDispatchSupports
private static Timer timer;
// When this WDS expires, we cancel the timer task leaving the
// shared timer up and running
private TimerTask timerTask;
private AtomicBoolean keepBlockingEDT = new AtomicBoolean(false);
private AtomicBoolean keepBlockingCT = new AtomicBoolean(false);
private static synchronized void initializeTimer() {
if (timer == null) {
timer = new Timer("AWT-WaitDispatchSupport-Timer", true);
}
}
/**
* Creates a {@code WaitDispatchSupport} instance to
* serve the given event dispatch thread.
*
* @param dispatchThread An event dispatch thread that
* should not stop dispatching events while waiting
*
* @since 1.7
*/
public WaitDispatchSupport(EventDispatchThread dispatchThread) {
this(dispatchThread, null);
}
/**
* Creates a {@code WaitDispatchSupport} instance to
* serve the given event dispatch thread.
*
* @param dispatchThread An event dispatch thread that
* should not stop dispatching events while waiting
* @param extCondition A conditional object used to determine
* if the loop should be terminated
*
* @since 1.7
*/
public WaitDispatchSupport(EventDispatchThread dispatchThread,
Conditional extCond)
{
if (dispatchThread == null) {
throw new IllegalArgumentException("The dispatchThread can not be null");
}
this.dispatchThread = dispatchThread;
this.extCondition = extCond;
this.condition = new Conditional() {
@Override
public boolean evaluate() {
if (log.isLoggable(PlatformLogger.FINEST)) {
log.finest("evaluate(): blockingEDT=" + keepBlockingEDT.get() +
", blockingCT=" + keepBlockingCT.get());
}
boolean extEvaluate =
(extCondition != null) ? extCondition.evaluate() : true;
if (!keepBlockingEDT.get() || !extEvaluate) {
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
}
return false;
}
return true;
}
};
}
/**
* Creates a {@code WaitDispatchSupport} instance to
* serve the given event dispatch thread.
* <p>
* The {@link EventFilter} is set on the {@code dispatchThread}
* while waiting. The filter is removed on completion of the
* waiting process.
* <p>
*
*
* @param dispatchThread An event dispatch thread that
* should not stop dispatching events while waiting
* @param filter {@code EventFilter} to be set
* @param interval A time interval to wait for. Note that
* when the waiting process takes place on EDT
* there is no guarantee to stop it in the given time
*
* @since 1.7
*/
public WaitDispatchSupport(EventDispatchThread dispatchThread,
Conditional extCondition,
EventFilter filter, long interval)
{
this(dispatchThread, extCondition);
this.filter = filter;
if (interval < 0) {
throw new IllegalArgumentException("The interval value must be >= 0");
}
this.interval = interval;
if (interval != 0) {
initializeTimer();
}
}
/**
* @inheritDoc
*/
@Override
public boolean enter() {
log.fine("enter(): blockingEDT=" + keepBlockingEDT.get() +
", blockingCT=" + keepBlockingCT.get());
if (!keepBlockingEDT.compareAndSet(false, true)) {
log.fine("The secondary loop is already running, aborting");
return false;
}
final Runnable run = new Runnable() {
public void run() {
log.fine("Starting a new event pump");
if (filter == null) {
dispatchThread.pumpEvents(condition);
} else {
dispatchThread.pumpEventsForFilter(condition, filter);
}
}
};
// We have two mechanisms for blocking: if we're on the
// dispatch thread, start a new event pump; if we're
// on any other thread, call wait() on the treelock
Thread currentThread = Thread.currentThread();
if (currentThread == dispatchThread) {
log.finest("On dispatch thread: " + dispatchThread);
if (interval != 0) {
log.finest("scheduling the timer for " + interval + " ms");
timer.schedule(timerTask = new TimerTask() {
@Override
public void run() {
if (keepBlockingEDT.compareAndSet(true, false)) {
wakeupEDT();
}
}
}, interval);
}
// Dispose SequencedEvent we are dispatching on the the current
// AppContext, to prevent us from hang - see 4531693 for details
SequencedEvent currentSE = KeyboardFocusManager.
getCurrentKeyboardFocusManager().getCurrentSequencedEvent();
if (currentSE != null) {
log.fine("Dispose current SequencedEvent: " + currentSE);
currentSE.dispose();
}
// In case the exit() method is called before starting
// new event pump it will post the waking event to EDT.
// The event will be handled after the the new event pump
// starts. Thus, the enter() method will not hang.
//
// Event pump should be privileged. See 6300270.
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
run.run();
return null;
}
});
} else {
log.finest("On non-dispatch thread: " + currentThread);
synchronized (getTreeLock()) {
if (filter != null) {
dispatchThread.addEventFilter(filter);
}
try {
EventQueue eq = dispatchThread.getEventQueue();
eq.postEvent(new PeerEvent(this, run, PeerEvent.PRIORITY_EVENT));
keepBlockingCT.set(true);
if (interval > 0) {
long currTime = System.currentTimeMillis();
while (keepBlockingCT.get() &&
((extCondition != null) ? extCondition.evaluate() : true) &&
(currTime + interval > System.currentTimeMillis()))
{
getTreeLock().wait(interval);
}
} else {
while (keepBlockingCT.get() &&
((extCondition != null) ? extCondition.evaluate() : true))
{
getTreeLock().wait();
}
}
log.fine("waitDone " + keepBlockingEDT.get() + " " + keepBlockingCT.get());
} catch (InterruptedException e) {
log.fine("Exception caught while waiting: " + e);
} finally {
if (filter != null) {
dispatchThread.removeEventFilter(filter);
}
}
// If the waiting process has been stopped because of the
// time interval passed or an exception occurred, the state
// should be changed
keepBlockingEDT.set(false);
keepBlockingCT.set(false);
}
}
return true;
}
/**
* @inheritDoc
*/
public boolean exit() {
log.fine("exit(): blockingEDT=" + keepBlockingEDT.get() +
", blockingCT=" + keepBlockingCT.get());
if (keepBlockingEDT.compareAndSet(true, false)) {
wakeupEDT();
return true;
}
return false;
}
private final static Object getTreeLock() {
return Component.LOCK;
}
private final Runnable wakingRunnable = new Runnable() {
public void run() {
log.fine("Wake up EDT");
synchronized (getTreeLock()) {
keepBlockingCT.set(false);
getTreeLock().notifyAll();
}
log.fine("Wake up EDT done");
}
};
private void wakeupEDT() {
log.finest("wakeupEDT(): EDT == " + dispatchThread);
EventQueue eq = dispatchThread.getEventQueue();
eq.postEvent(new PeerEvent(this, wakingRunnable, PeerEvent.PRIORITY_EVENT));
}
}
/*
* Copyright (c) 2010, 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.
*
* 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.
*/
/*
@test
@bug 6949936
@author Artem Ananiev: area=eventqueue
@run main/timeout=30 SecondaryLoopTest
*/
import java.awt.*;
/**
* Unit test for java.awt.SecondaryLoop implementation
*/
public class SecondaryLoopTest {
private static volatile boolean loopStarted;
private static volatile boolean doubleEntered;
private static volatile boolean loopActive;
private static volatile boolean eventDispatched;
public static void main(String[] args) throws Exception {
test(true, true);
test(true, false);
test(false, true);
test(false, false);
}
private static void test(final boolean enterEDT, final boolean exitEDT) throws Exception {
System.out.println("Running test(" + enterEDT + ", " + exitEDT + ")");
System.err.flush();
loopStarted = true;
Runnable enterRun = new Runnable() {
@Override
public void run() {
Toolkit tk = Toolkit.getDefaultToolkit();
EventQueue eq = tk.getSystemEventQueue();
final SecondaryLoop loop = eq.createSecondaryLoop();
doubleEntered = false;
eventDispatched = false;
Runnable eventRun = new Runnable() {
@Override
public void run() {
// Let the loop enter
sleep(1000);
if (loop.enter()) {
doubleEntered = true;
}
eventDispatched = true;
}
};
EventQueue.invokeLater(eventRun);
Runnable exitRun = new Runnable() {
@Override
public void run() {
// Let the loop enter and eventRun finish
sleep(2000);
if (doubleEntered) {
// Hopefully, we get here if the loop is entered twice
loop.exit();
}
loop.exit();
}
};
if (exitEDT) {
EventQueue.invokeLater(exitRun);
} else {
new Thread(exitRun).start();
}
if (!loop.enter()) {
loopStarted = false;
}
loopActive = eventDispatched;
}
};
if (enterEDT) {
EventQueue.invokeAndWait(enterRun);
} else {
enterRun.run();
}
// Print all the flags before we fail with exception
System.out.println(" loopStarted = " + loopStarted);
System.out.println(" doubleEntered = " + doubleEntered);
System.out.println(" loopActive = " + loopActive);
System.out.flush();
if (!loopStarted) {
throw new RuntimeException("Test FAILED: the secondary loop is not started");
}
if (doubleEntered) {
throw new RuntimeException("Test FAILED: the secondary loop is started twice");
}
if (!loopActive) {
throw new RuntimeException("Test FAILED: the secondary loop exited immediately");
}
}
private static void sleep(long t) {
try {
Thread.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册