diff --git a/tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF index f5bed800eee..9a91f01f75d 100644 --- a/tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF +++ b/tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF @@ -13,8 +13,7 @@ Export-Package: org.eclipse.jface.text.tests.rules, org.eclipse.jface.text.tests.source, org.eclipse.jface.text.tests.source.inlined, - org.eclipse.jface.text.tests.templates.persistence, - org.eclipse.jface.text.tests.util + org.eclipse.jface.text.tests.templates.persistence Require-Bundle: org.eclipse.jface.text;bundle-version="[3.20.0,4.0.0)", org.eclipse.jface;bundle-version="[3.5.0,4.0.0)", @@ -22,6 +21,7 @@ Require-Bundle: org.eclipse.text.tests;bundle-version="[3.5.0,4.0.0)", org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)", org.eclipse.ui.workbench.texteditor, + org.eclipse.ui.tests.harness, org.eclipse.test;bundle-version="3.6.200" Bundle-RequiredExecutionEnvironment: JavaSE-17 Eclipse-BundleShape: dir diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/MultiSelectionTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/MultiSelectionTest.java index 1c779680bce..62f89f36101 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/MultiSelectionTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/MultiSelectionTest.java @@ -43,7 +43,8 @@ import org.eclipse.jface.text.MultiTextSelection; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextViewer; -import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.tests.harness.util.DisplayHelper; public class MultiSelectionTest { diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextViewerTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextViewerTest.java index 45c3f9a1f23..b283a528c28 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextViewerTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextViewerTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2014, 2024 Google, Inc and others. + * Copyright (c) 2014, 2025 Google, Inc and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -71,7 +71,8 @@ import org.eclipse.jface.text.hyperlink.URLHyperlinkDetector; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.text.source.SourceViewerConfiguration; -import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.tests.harness.util.DisplayHelper; /** * Basic tests for TextViewer. @@ -289,7 +290,7 @@ public static void ctrlHome(ITextViewer viewer) { static void postKeyEvent(Control widget, int keyCode, int stateMask, int type) { Display display= widget.getDisplay(); widget.setFocus(); - DisplayHelper.driveEventQueue(display); + DisplayHelper.runEventLoop(display, 0); Event event = new Event(); event.widget = widget; event.keyCode = keyCode; @@ -301,7 +302,7 @@ static void postKeyEvent(Control widget, int keyCode, int stateMask, int type) { for (Listener listener : listeners) { listener.handleEvent(event); } - DisplayHelper.driveEventQueue(display); + DisplayHelper.runEventLoop(display, 0); } public static String generate5000Lines() { diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningLineHeaderAnnotationTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningLineHeaderAnnotationTest.java index 8895c961a08..e5473695362 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningLineHeaderAnnotationTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningLineHeaderAnnotationTest.java @@ -35,7 +35,8 @@ import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.text.source.inlined.AbstractInlinedAnnotation; import org.eclipse.jface.text.source.inlined.InlinedAnnotationSupport; -import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.tests.harness.util.DisplayHelper; public class CodeMiningLineHeaderAnnotationTest { diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningProjectionViewerTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningProjectionViewerTest.java index ef7b390936c..4143057ea5b 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningProjectionViewerTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningProjectionViewerTest.java @@ -60,7 +60,8 @@ import org.eclipse.jface.text.source.projection.ProjectionViewer; import org.eclipse.jface.text.tests.contentassist.BarContentAssistProcessor; import org.eclipse.jface.text.tests.source.inlined.LineContentBoundsDrawingTest.AccessAllAnnoations; -import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.tests.harness.util.DisplayHelper; public class CodeMiningProjectionViewerTest { @@ -193,7 +194,7 @@ public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) { fParent.setSize(200, 4 * fViewer.getTextWidget().getLineHeight()); //fParent.pack(true); fParent.open(); - DisplayHelper.driveEventQueue(fParent.getDisplay()); + DisplayHelper.runEventLoop(fParent.getDisplay(), 0); // ensure ViewportGuard is initialized fViewer.getControl().notifyListeners(SWT.KeyUp, new Event()); fViewer.setSelectedRange(1, 0); diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java index e512180a3b3..4ac04ebf52a 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019 Red Hat Inc. and others. + * Copyright (c) 2019, 2025 Red Hat Inc. and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -63,7 +63,8 @@ import org.eclipse.jface.text.source.AnnotationPainter; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.text.tests.TextViewerTest; -import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.tests.harness.util.DisplayHelper; public class CodeMiningTest { diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AbstractContentAssistTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AbstractContentAssistTest.java index 8d77eea581a..3059b0dc61a 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AbstractContentAssistTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AbstractContentAssistTest.java @@ -42,7 +42,8 @@ import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.text.source.SourceViewerConfiguration; -import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.tests.harness.util.DisplayHelper; public class AbstractContentAssistTest { @@ -65,7 +66,7 @@ public void setUp() { for (Shell s : shells) { s.dispose(); } - DisplayHelper.driveEventQueue(Display.getDefault()); + DisplayHelper.runEventLoop(Display.getDefault(), 0); } @After @@ -207,7 +208,7 @@ public Button getButton() { protected static void processEvents() { - DisplayHelper.driveEventQueue(getDisplay()); + DisplayHelper.runEventLoop(getDisplay(), 0); } private static Display getDisplay() { diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AsyncContentAssistTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AsyncContentAssistTest.java index 6f293214561..63793379930 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AsyncContentAssistTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AsyncContentAssistTest.java @@ -47,7 +47,8 @@ import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.contentassist.ContentAssistant; import org.eclipse.jface.text.source.SourceViewer; -import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.tests.harness.util.DisplayHelper; public class AsyncContentAssistTest { @@ -117,7 +118,7 @@ public void testCompletePrefix() { contentAssistant.enablePrefixCompletion(true); contentAssistant.install(viewer); shell.open(); - DisplayHelper.driveEventQueue(shell.getDisplay()); + DisplayHelper.runEventLoop(shell.getDisplay(), 0); Display display= shell.getDisplay(); final Collection beforeShells= AbstractContentAssistTest.getCurrentShells(); contentAssistant.showPossibleCompletions(); diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/FilteringAsyncContentAssistTests.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/FilteringAsyncContentAssistTests.java index c5ebcc14063..7b0de15b10e 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/FilteringAsyncContentAssistTests.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/FilteringAsyncContentAssistTests.java @@ -50,7 +50,8 @@ import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.text.contentassist.IContextInformationValidator; import org.eclipse.jface.text.source.SourceViewer; -import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.tests.harness.util.DisplayHelper; /** * Tests for Async completion proposal popup proposals filtering mechanics @@ -70,7 +71,7 @@ public void setup() { shell = new Shell(); shell.setSize(300, 300); shell.open(); - DisplayHelper.driveEventQueue(shell.getDisplay()); + DisplayHelper.runEventLoop(shell.getDisplay(), 0); viewer = new SourceViewer(shell, null, SWT.NONE); Document document = new Document(); diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/IncrementalAsyncContentAssistTests.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/IncrementalAsyncContentAssistTests.java index 4fb4497ac9c..4bf8a0fd924 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/IncrementalAsyncContentAssistTests.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/IncrementalAsyncContentAssistTests.java @@ -28,7 +28,8 @@ import org.eclipse.jface.text.contentassist.ContentAssistant; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.source.SourceViewer; -import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.tests.harness.util.DisplayHelper; public class IncrementalAsyncContentAssistTests { @@ -45,7 +46,7 @@ public void setup() { shell= new Shell(); shell.setSize(300, 300); shell.open(); - DisplayHelper.driveEventQueue(shell.getDisplay()); + DisplayHelper.runEventLoop(shell.getDisplay(), 0); viewer= new SourceViewer(shell, null, SWT.NONE); Document document= new Document(); diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/AnnotationRulerColumnTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/AnnotationRulerColumnTest.java index 419570ab305..1d1867c9287 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/AnnotationRulerColumnTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/AnnotationRulerColumnTest.java @@ -39,7 +39,8 @@ import org.eclipse.jface.text.source.IAnnotationAccess; import org.eclipse.jface.text.source.IAnnotationAccessExtension; import org.eclipse.jface.text.source.projection.ProjectionViewer; -import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.tests.harness.util.DisplayHelper; public class AnnotationRulerColumnTest { diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/AnnotationOnTabTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/AnnotationOnTabTest.java index 279a6fb8ccd..f5bfeafc0e2 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/AnnotationOnTabTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/AnnotationOnTabTest.java @@ -36,7 +36,8 @@ import org.eclipse.jface.text.source.inlined.LineContentAnnotation; import org.eclipse.jface.text.tests.source.inlined.LineContentBoundsDrawingTest.AccessAllAnnoations; import org.eclipse.jface.text.tests.source.inlined.LineContentBoundsDrawingTest.TestAnnotationPainter; -import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.tests.harness.util.DisplayHelper; public class AnnotationOnTabTest { diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/LineContentBoundsDrawingTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/LineContentBoundsDrawingTest.java index 818deb75cfd..70ddec1973f 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/LineContentBoundsDrawingTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/LineContentBoundsDrawingTest.java @@ -41,7 +41,8 @@ import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.text.source.inlined.InlinedAnnotationSupport; import org.eclipse.jface.text.source.inlined.LineContentAnnotation; -import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.tests.harness.util.DisplayHelper; /** * This test verify that the bounds of the text as returned by StyledText.getTextBounds() diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/util/DisplayHelper.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/util/DisplayHelper.java deleted file mode 100644 index f4b53913a41..00000000000 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/util/DisplayHelper.java +++ /dev/null @@ -1,581 +0,0 @@ -package org.eclipse.jface.text.tests.util; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.junit.Assert; - -import org.eclipse.swt.widgets.Display; - - -/** - * Runs the event loop of the given display until {@link #condition()} becomes - * true or no events have occurred for the supplied timeout. - * Between running the event loop, {@link Display#sleep()} is called. - *

- * There is a caveat: the given timeouts must be long enough that the calling - * thread can enter Display.sleep() before the timeout elapses, - * otherwise, the waiter may time out before sleep is called and - * the sleeping thread may never be waken up. - *

- * - * @since 3.11 - */ -public abstract class DisplayHelper { -// copy of org.eclipse.jdt.testplugin.util.DisplayHelper in org.eclipse.jdt.ui.tests - - /** - * Controls if the timeout is used. For debugging use true, false otherwise - */ - private static final boolean DISABLE_TIMEOUT= false; - - /** - * Creates a new instance. - */ - protected DisplayHelper() { - } - - /** - * Until {@link #condition()} becomes true or the timeout - * elapses, call {@link Display#sleep()} and run the event loop. - *

- * If timeout < 0, the event loop is never driven and - * only the condition is checked. If timeout == 0, the event - * loop is driven at most once, but Display.sleep() is never - * invoked. - *

- * - * @param display the display to run the event loop of - * @param timeout the timeout in milliseconds - * @return true if the condition became true, - * false if the timeout elapsed - */ - public final boolean waitForCondition(Display display, long timeout) { - // if the condition already holds, succeed - if (condition()) - return true; - - if (timeout < 0) - return false; - - // if driving the event loop once makes the condition hold, succeed - // without spawning a thread. - driveEventQueue(display); - if (condition()) - return true; - - // if the timeout is negative or zero, fail - if (timeout == 0) - return false; - - // repeatedly sleep until condition becomes true or timeout elapses - DisplayWaiter waiter= new DisplayWaiter(display); - DisplayWaiter.Timeout timeoutState= waiter.start(timeout); - boolean condition; - try { - do { - if (display.sleep()) - driveEventQueue(display); - condition= condition(); - } while (!condition && !timeoutState.hasTimedOut()); - } finally { - waiter.stop(); - } - return condition; - } - - /** - * Call {@link Display#sleep()} and run the event loop until the given - * timeout has elapsed. - *

- * If timeout < 0, nothing happens. If - * timeout == 0, the event loop is driven exactly once, but - * Display.sleep() is never invoked. - *

- * - * @param display the display to run the event loop of - * @param millis the timeout in milliseconds - */ - public static void sleep(Display display, long millis) { - new DisplayHelper() { - @Override - public boolean condition() { - return false; - } - }.waitForCondition(display, millis); - } - - /** - * The condition which has to be met in order for {@link #waitForCondition(Display, long)} to - * return before the timeout elapses. - * - * @return true if the condition is met, false if the event loop - * should be driven some more - */ - protected abstract boolean condition(); - - /** - * Runs the event loop on the given display. - * - * @param display the display - * @return if display.readAndDispatch returned - * true at least once - */ - public static boolean driveEventQueue(Display display) { - boolean events= false; - while (display.readAndDispatch()) { - events= true; - } - return events; - } - - /** - * Until {@link #condition()} becomes true or the timeout - * elapses, call {@link Display#sleep()} and run the event loop. - *

- * If timeout < 0, the event loop is never driven and - * only the condition is checked. If timeout == 0, the event - * loop is driven at most once, but Display.sleep() is never - * invoked. - *

- *

- * The condition gets rechecked every interval milliseconds, even - * if no events were read from the queue. - *

- * - * @param display the display to run the event loop of - * @param timeout the timeout in milliseconds - * @param interval the interval to re-check the condition in milliseconds - * @return true if the condition became true, - * false if the timeout elapsed - */ - public final boolean waitForCondition(Display display, long timeout, long interval) { - // if the condition already holds, succeed - if (condition()) - return true; - - if (timeout < 0) - return false; - - // if driving the event loop once makes the condition hold, succeed - // without spawning a thread. - driveEventQueue(display); - if (condition()) - return true; - - // if the timeout is negative or zero, fail - if (timeout == 0) - return false; - - // repeatedly sleep until condition becomes true or timeout elapses - DisplayWaiter waiter= new DisplayWaiter(display, true); - long currentTimeMillis= System.currentTimeMillis(); - long finalTimeout= timeout + currentTimeMillis; - if (finalTimeout < currentTimeMillis) - finalTimeout= Long.MAX_VALUE; - boolean condition; - try { - do { - waiter.restart(interval); - if (display.sleep()) - driveEventQueue(display); - condition= condition(); - } while (!condition && (DISABLE_TIMEOUT || finalTimeout > System.currentTimeMillis())); - } finally { - waiter.stop(); - } - return condition; - } - -} - -/** - * Implements the thread that will wait for the timeout and wake up the display - * so it does not wait forever. The thread may be restarted after it was stopped - * or timed out. - */ -final class DisplayWaiter { - /** - * Timeout state of a display waiter thread. - */ - public final class Timeout { - private boolean fTimeoutState= false; - /** - * Returns true if the timeout has been reached, - * false if not. - * - * @return true if the timeout has been reached, - * false if not - */ - public boolean hasTimedOut() { - synchronized (fMutex) { - return fTimeoutState; - } - } - void setTimedOut(boolean timedOut) { - fTimeoutState= timedOut; - } - Timeout(boolean initialState) { - fTimeoutState= initialState; - } - } - - // configuration - private final Display fDisplay; - private final Object fMutex= new Object(); - private final boolean fKeepRunningOnTimeout; - - /* State -- possible transitions: - * - * STOPPED -> RUNNING - * RUNNING -> STOPPED - * RUNNING -> IDLE - * IDLE -> RUNNING - * IDLE -> STOPPED - */ - private static final int RUNNING= 1 << 1; - private static final int STOPPED= 1 << 2; - private static final int IDLE= 1 << 3; - - /** The current state. */ - private int fState; - /** The time in milliseconds (see Date) that the timeout will occur. */ - private long fNextTimeout; - /** The thread. */ - private Thread fCurrentThread; - /** The timeout state of the current thread. */ - private Timeout fCurrentTimeoutState; - - /** - * Creates a new instance on the given display and timeout. - * - * @param display the display to run the event loop of - */ - public DisplayWaiter(Display display) { - this(display, false); - } - - /** - * Creates a new instance on the given display and timeout. - * - * @param display the display to run the event loop of - * @param keepRunning true if the thread should be kept - * running after timing out - */ - public DisplayWaiter(Display display, boolean keepRunning) { - Assert.assertNotNull(display); - fDisplay= display; - fState= STOPPED; - fKeepRunningOnTimeout= keepRunning; - } - - /** - * Starts the timeout thread if it is not currently running. Nothing happens - * if a thread is already running. - * - * @param delay the delay from now in milliseconds - * @return the timeout state which can be queried for its timed out status - */ - @SuppressWarnings("incomplete-switch") - public Timeout start(long delay) { - Assert.assertTrue(delay > 0); - synchronized (fMutex) { - switch (fState) { - case STOPPED: - startThread(); - setNextTimeout(delay); - break; - case IDLE: - unhold(); - setNextTimeout(delay); - break; - } - - return fCurrentTimeoutState; - } - } - - /** - * Sets the next timeout to current time plus delay. - * - * @param delay the delay until the next timeout occurs in milliseconds from - * now - */ - private void setNextTimeout(long delay) { - long currentTimeMillis= System.currentTimeMillis(); - long next= currentTimeMillis + delay; - if (next > currentTimeMillis) - fNextTimeout= next; - else - fNextTimeout= Long.MAX_VALUE; - } - - /** - * Starts the thread if it is not currently running; resets the timeout if - * it is. - * - * @param delay the delay from now in milliseconds - * @return the timeout state which can be queried for its timed out status - */ - @SuppressWarnings("incomplete-switch") - public Timeout restart(long delay) { - Assert.assertTrue(delay > 0); - synchronized (fMutex) { - switch (fState) { - case STOPPED: - startThread(); - break; - case IDLE: - unhold(); - break; - } - setNextTimeout(delay); - - return fCurrentTimeoutState; - } - } - - /** - * Stops the thread if it is running. If not, nothing happens. Another - * thread may be started by calling {@link #start(long)} or - * {@link #restart(long)}. - */ - public void stop() { - synchronized (fMutex) { - if (tryTransition(RUNNING | IDLE, STOPPED)) - fMutex.notifyAll(); - } - } - - /** - * Puts the reaper thread on hold but does not stop it. It may be restarted - * by calling {@link #start(long)} or {@link #restart(long)}. - */ - public void hold() { - synchronized (fMutex) { - // nothing to do if there is no thread - if (tryTransition(RUNNING, IDLE)) - fMutex.notifyAll(); - } - } - - /** - * Transition to RUNNING and clear the timed out flag. Assume - * current state is IDLE. - */ - private void unhold() { - checkedTransition(IDLE, RUNNING); - fCurrentTimeoutState= new Timeout(false); - fMutex.notifyAll(); - } - - /** - * Start the thread. Assume the current state is STOPPED. - */ - private void startThread() { - checkedTransition(STOPPED, RUNNING); - fCurrentTimeoutState= new Timeout(false); - fCurrentThread= new Thread() { - /** - * Exception thrown when a thread notices that it has been stopped - * and a new thread has been started. - */ - final class ThreadChangedException extends Exception { - private static final long serialVersionUID= 1L; - } - - /* - * @see java.lang.Runnable#run() - */ - @Override - public void run() { - try { - run2(); - } catch (InterruptedException e) { - // ignore and end the thread - we never interrupt ourselves, - // so it must be an external entity that interrupted us - Logger.getGlobal().log(Level.FINE, "", e); - } catch (ThreadChangedException e) { - // the thread was stopped and restarted before we got out - // of a wait - we're no longer used - // we might have been notified instead of the current thread, - // so wake it up - Logger.getGlobal().log(Level.FINE, "", e); - synchronized (fMutex) { - fMutex.notifyAll(); - } - } - } - - /** - * Runs the thread. - * - * @throws InterruptedException if the thread was interrupted - * @throws ThreadChangedException if the thread changed - */ - private void run2() throws InterruptedException, ThreadChangedException { - synchronized (fMutex) { - checkThread(); - tryHold(); // wait / potential state change - assertStates(STOPPED | RUNNING); - - while (isState(RUNNING)) { - waitForTimeout(); // wait / potential state change - - if (isState(RUNNING)) - timedOut(); // state change - assertStates(STOPPED | IDLE); - - tryHold(); // wait / potential state change - assertStates(STOPPED | RUNNING); - } - assertStates(STOPPED); - } - } - - /** - * Check whether the current thread is this thread, throw an - * exception otherwise. - * - * @throws ThreadChangedException if the current thread changed - */ - private void checkThread() throws ThreadChangedException { - if (fCurrentThread != this) - throw new ThreadChangedException(); - } - - /** - * Waits until the next timeout occurs. - * - * @throws InterruptedException if the thread was interrupted - * @throws ThreadChangedException if the thread changed - */ - private void waitForTimeout() throws InterruptedException, ThreadChangedException { - long delta; - while (isState(RUNNING) && (delta = fNextTimeout - System.currentTimeMillis()) > 0) { - delta= Math.max(delta, 50); // wait at least 50ms in order to avoid timing out before the display is going to sleep - Logger.getGlobal().finest("sleeping for " + delta + "ms"); - fMutex.wait(delta); - checkThread(); - } - } - - /** - * Sets the timed out flag and wakes up the display. Transitions to - * IDLE (if in keep-running mode) or - * STOPPED. - */ - private void timedOut() { - Logger.getGlobal().finer("timed out"); - fCurrentTimeoutState.setTimedOut(true); - fDisplay.wake(); // wake up call! - if (fKeepRunningOnTimeout) - checkedTransition(RUNNING, IDLE); - else - checkedTransition(RUNNING, STOPPED); - } - - /** - * Waits while the state is IDLE, then returns. The - * state must not be RUNNING when calling this - * method. The state is either STOPPED or - * RUNNING when the method returns. - * - * @throws InterruptedException if the thread was interrupted - * @throws ThreadChangedException if the thread has changed while on - * hold - */ - private void tryHold() throws InterruptedException, ThreadChangedException { - while (isState(IDLE)) { - fMutex.wait(0); - checkThread(); - } - assertStates(STOPPED | RUNNING); - } - }; - - fCurrentThread.start(); - } - - /** - * Transitions to nextState if the current state is one of - * possibleStates. Returns true if the - * transition happened, false otherwise. - * - * @param possibleStates the states which trigger a transition - * @param nextState the state to transition to - * @return true if the transition happened, - * false otherwise - */ - private boolean tryTransition(int possibleStates, int nextState) { - if (isState(possibleStates)) { - Logger.getGlobal().finer(name(fState) + " > " + name(nextState) + " (" + name(possibleStates) + ")"); - fState= nextState; - return true; - } - Logger.getGlobal().finest("noTransition" + name(fState) + " !> " + name(nextState) + " (" + name(possibleStates) + ")"); - return false; - } - - /** - * Checks the possibleStates and throws an assertion if it is - * not met, then transitions to nextState. - * - * @param possibleStates the allowed states - * @param nextState the state to transition to - */ - private void checkedTransition(int possibleStates, int nextState) { - assertStates(possibleStates); - Logger.getGlobal().finer(name(fState) + " > " + name(nextState)); - fState= nextState; - } - - /** - * Implements state consistency checking. - * - * @param states the allowed states - */ - private void assertStates(int states) { - Assert.assertTrue("illegal state", isState(states)); - } - - /** - * Answers true if the current state is in the given - * states. - * - * @param states the possible states - * @return true if the current state is in the given states, - * false otherwise - */ - private boolean isState(int states) { - return (states & fState) == fState; - } - - /** - * Pretty print the given states. - * - * @param states the states - * @return a string representation of the states - */ - private String name(int states) { - StringBuilder buf= new StringBuilder(); - boolean comma= false; - if ((states & RUNNING) == RUNNING) { - buf.append("RUNNING"); - comma= true; - } - if ((states & STOPPED) == STOPPED) { - if (comma) - buf.append(","); - buf.append("STOPPED"); - comma= true; - } - if ((states & IDLE) == IDLE) { - if (comma) - buf.append(","); - buf.append("IDLE"); - } - return buf.toString(); - } - -}