Mercurial > hg > icedtea9-forest > jdk
changeset 2840:7e26538596be
6949936: Provide API for running nested events loops, similar to what modal dialogs do
Reviewed-by: ant, anthony
author | art |
---|---|
date | Tue, 24 Aug 2010 12:54:46 +0400 |
parents | 3b0abcb51280 |
children | d3fdf9e7e9c2 |
files | src/share/classes/java/awt/Dialog.java src/share/classes/java/awt/EventDispatchThread.java src/share/classes/java/awt/EventQueue.java src/share/classes/java/awt/SecondaryLoop.java src/share/classes/java/awt/WaitDispatchSupport.java test/java/awt/EventQueue/SecondaryLoopTest/SecondaryLoopTest.java |
diffstat | 6 files changed, 629 insertions(+), 98 deletions(-) [+] |
line wrap: on
line diff
--- a/src/share/classes/java/awt/Dialog.java Mon Aug 09 16:02:19 2010 -0700 +++ b/src/share/classes/java/awt/Dialog.java Tue Aug 24 12:54:46 2010 +0400 @@ -277,10 +277,8 @@ */ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ } } 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; }
--- a/src/share/classes/java/awt/EventDispatchThread.java Mon Aug 09 16:02:19 2010 -0700 +++ b/src/share/classes/java/awt/EventDispatchThread.java Tue Aug 24 12:54:46 2010 +0400 @@ -113,8 +113,7 @@ 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 @@ 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 @@ } 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 @@ } void removeEventFilter(EventFilter filter) { + eventLog.finest("removing the event filter: " + filter); synchronized (eventFilters) { eventFilters.remove(filter); }
--- a/src/share/classes/java/awt/EventQueue.java Mon Aug 09 16:02:19 2010 -0700 +++ b/src/share/classes/java/awt/EventQueue.java Tue Aug 24 12:54:46 2010 +0400 @@ -884,6 +884,41 @@ } /** + * 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 * dispatch thread. Use this method to ensure that a particular
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/java/awt/SecondaryLoop.java Tue Aug 24 12:54:46 2010 +0400 @@ -0,0 +1,147 @@ +/* + * 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(); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/java/awt/WaitDispatchSupport.java Tue Aug 24 12:54:46 2010 +0400 @@ -0,0 +1,302 @@ +/* + * 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)); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/java/awt/EventQueue/SecondaryLoopTest/SecondaryLoopTest.java Tue Aug 24 12:54:46 2010 +0400 @@ -0,0 +1,126 @@ +/* + * 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); + } + } + +}