blob: 3517ad6d29f4e62dcfd5ec761c948057c00969f4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.tests.navigator;
import java.util.logging.Level;
import java.util.logging.Logger;
import junit.framework.Assert;
import org.eclipse.swt.widgets.Display;
/**
* 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.
*
* @since 3.1
*/
final class DisplayWaiter {
/**
* Timeout state of a display waiter thread.
*/
public final class Timeout {
boolean fTimeoutState= false;
/**
* Returns <code>true</code> if the timeout has been reached,
* <code>false</code> if not.
*
* @return <code>true</code> if the timeout has been reached,
* <code>false</code> 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 <code>true</code> 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
*/
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 <em>current time</em> plus <code>delay</code>.
*
* @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
*/
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 <code>RUNNING</code> and clear the timed out flag. Assume
* current state is <code>IDLE</code>.
*/
private void unhold() {
checkedTransition(IDLE, RUNNING);
fCurrentTimeoutState= new Timeout(false);
fMutex.notifyAll();
}
/**
* Start the thread. Assume the current state is <code>STOPPED</code>.
*/
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()
*/
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.global.log(Level.FINE, "", e); //$NON-NLS-1$
} 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.global.log(Level.FINE, "", e); //$NON-NLS-1$
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.global.finest("sleeping for " + delta + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
fMutex.wait(delta);
checkThread();
}
}
/**
* Sets the timed out flag and wakes up the display. Transitions to
* <code>IDLE</code> (if in keep-running mode) or
* <code>STOPPED</code>.
*/
private void timedOut() {
Logger.global.finer("timed out"); //$NON-NLS-1$
fCurrentTimeoutState.setTimedOut(true);
fDisplay.wake(); // wake up call!
if (fKeepRunningOnTimeout)
checkedTransition(RUNNING, IDLE);
else
checkedTransition(RUNNING, STOPPED);
}
/**
* Waits while the state is <code>IDLE</code>, then returns. The
* state must not be <code>RUNNING</code> when calling this
* method. The state is either <code>STOPPED</code> or
* <code>RUNNING</code> 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 <code>nextState</code> if the current state is one of
* <code>possibleStates</code>. Returns <code>true</code> if the
* transition happened, <code>false</code> otherwise.
*
* @param possibleStates the states which trigger a transition
* @param nextState the state to transition to
* @return <code>true</code> if the transition happened,
* <code>false</code> otherwise
*/
private boolean tryTransition(int possibleStates, int nextState) {
if (isState(possibleStates)) {
Logger.global.finer(name(fState) + " > " + name(nextState) + " (" + name(possibleStates) + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
fState= nextState;
return true;
}
Logger.global.finest("noTransition" + name(fState) + " !> " + name(nextState) + " (" + name(possibleStates) + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
return false;
}
/**
* Checks the <code>possibleStates</code> and throws an assertion if it is
* not met, then transitions to <code>nextState</code>.
*
* @param possibleStates the allowed states
* @param nextState the state to transition to
*/
private void checkedTransition(int possibleStates, int nextState) {
assertStates(possibleStates);
Logger.global.finer(name(fState) + " > " + name(nextState)); //$NON-NLS-1$
fState= nextState;
}
/**
* Implements state consistency checking.
*
* @param states the allowed states
* @throws junit.framework.AssertionFailedError if the current state is not
* in <code>states</code>
*/
private void assertStates(int states) {
Assert.assertTrue("illegal state", isState(states)); //$NON-NLS-1$
}
/**
* Answers <code>true</code> if the current state is in the given
* <code>states</code>.
*
* @param states the possible states
* @return <code>true</code> if the current state is in the given states,
* <code>false</code> 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) {
StringBuffer buf= new StringBuffer();
boolean comma= false;
if ((states & RUNNING) == RUNNING) {
buf.append("RUNNING"); //$NON-NLS-1$
comma= true;
}
if ((states & STOPPED) == STOPPED) {
if (comma)
buf.append(","); //$NON-NLS-1$
buf.append("STOPPED"); //$NON-NLS-1$
comma= true;
}
if ((states & IDLE) == IDLE) {
if (comma)
buf.append(","); //$NON-NLS-1$
buf.append("IDLE"); //$NON-NLS-1$
}
return buf.toString();
}
}