001: /*******************************************************************************
002: * Copyright (c) 2005 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.tests.navigator;
011:
012: import java.util.logging.Level;
013: import java.util.logging.Logger;
014:
015: import junit.framework.Assert;
016:
017: import org.eclipse.swt.widgets.Display;
018:
019: /**
020: * Implements the thread that will wait for the timeout and wake up the display
021: * so it does not wait forever. The thread may be restarted after it was stopped
022: * or timed out.
023: *
024: * @since 3.1
025: */
026: final class DisplayWaiter {
027: /**
028: * Timeout state of a display waiter thread.
029: */
030: public final class Timeout {
031: boolean fTimeoutState = false;
032:
033: /**
034: * Returns <code>true</code> if the timeout has been reached,
035: * <code>false</code> if not.
036: *
037: * @return <code>true</code> if the timeout has been reached,
038: * <code>false</code> if not
039: */
040: public boolean hasTimedOut() {
041: synchronized (fMutex) {
042: return fTimeoutState;
043: }
044: }
045:
046: void setTimedOut(boolean timedOut) {
047: fTimeoutState = timedOut;
048: }
049:
050: Timeout(boolean initialState) {
051: fTimeoutState = initialState;
052: }
053: }
054:
055: // configuration
056: private final Display fDisplay;
057: private final Object fMutex = new Object();
058: private final boolean fKeepRunningOnTimeout;
059:
060: /* State -- possible transitions:
061: *
062: * STOPPED -> RUNNING
063: * RUNNING -> STOPPED
064: * RUNNING -> IDLE
065: * IDLE -> RUNNING
066: * IDLE -> STOPPED
067: */
068: private static final int RUNNING = 1 << 1;
069: private static final int STOPPED = 1 << 2;
070: private static final int IDLE = 1 << 3;
071:
072: /** The current state. */
073: private int fState;
074: /** The time in milliseconds (see Date) that the timeout will occur. */
075: private long fNextTimeout;
076: /** The thread. */
077: private Thread fCurrentThread;
078: /** The timeout state of the current thread. */
079: private Timeout fCurrentTimeoutState;
080:
081: /**
082: * Creates a new instance on the given display and timeout.
083: *
084: * @param display the display to run the event loop of
085: */
086: public DisplayWaiter(Display display) {
087: this (display, false);
088: }
089:
090: /**
091: * Creates a new instance on the given display and timeout.
092: *
093: * @param display the display to run the event loop of
094: * @param keepRunning <code>true</code> if the thread should be kept
095: * running after timing out
096: */
097: public DisplayWaiter(Display display, boolean keepRunning) {
098: Assert.assertNotNull(display);
099: fDisplay = display;
100: fState = STOPPED;
101: fKeepRunningOnTimeout = keepRunning;
102: }
103:
104: /**
105: * Starts the timeout thread if it is not currently running. Nothing happens
106: * if a thread is already running.
107: *
108: * @param delay the delay from now in milliseconds
109: * @return the timeout state which can be queried for its timed out status
110: */
111: public Timeout start(long delay) {
112: Assert.assertTrue(delay > 0);
113: synchronized (fMutex) {
114: switch (fState) {
115: case STOPPED:
116: startThread();
117: setNextTimeout(delay);
118: break;
119: case IDLE:
120: unhold();
121: setNextTimeout(delay);
122: break;
123: }
124:
125: return fCurrentTimeoutState;
126: }
127: }
128:
129: /**
130: * Sets the next timeout to <em>current time</em> plus <code>delay</code>.
131: *
132: * @param delay the delay until the next timeout occurs in milliseconds from
133: * now
134: */
135: private void setNextTimeout(long delay) {
136: long currentTimeMillis = System.currentTimeMillis();
137: long next = currentTimeMillis + delay;
138: if (next > currentTimeMillis)
139: fNextTimeout = next;
140: else
141: fNextTimeout = Long.MAX_VALUE;
142: }
143:
144: /**
145: * Starts the thread if it is not currently running; resets the timeout if
146: * it is.
147: *
148: * @param delay the delay from now in milliseconds
149: * @return the timeout state which can be queried for its timed out status
150: */
151: public Timeout restart(long delay) {
152: Assert.assertTrue(delay > 0);
153: synchronized (fMutex) {
154: switch (fState) {
155: case STOPPED:
156: startThread();
157: break;
158: case IDLE:
159: unhold();
160: break;
161: }
162: setNextTimeout(delay);
163:
164: return fCurrentTimeoutState;
165: }
166: }
167:
168: /**
169: * Stops the thread if it is running. If not, nothing happens. Another
170: * thread may be started by calling {@link #start(long)} or
171: * {@link #restart(long)}.
172: */
173: public void stop() {
174: synchronized (fMutex) {
175: if (tryTransition(RUNNING | IDLE, STOPPED))
176: fMutex.notifyAll();
177: }
178: }
179:
180: /**
181: * Puts the reaper thread on hold but does not stop it. It may be restarted
182: * by calling {@link #start(long)} or {@link #restart(long)}.
183: */
184: public void hold() {
185: synchronized (fMutex) {
186: // nothing to do if there is no thread
187: if (tryTransition(RUNNING, IDLE))
188: fMutex.notifyAll();
189: }
190: }
191:
192: /**
193: * Transition to <code>RUNNING</code> and clear the timed out flag. Assume
194: * current state is <code>IDLE</code>.
195: */
196: private void unhold() {
197: checkedTransition(IDLE, RUNNING);
198: fCurrentTimeoutState = new Timeout(false);
199: fMutex.notifyAll();
200: }
201:
202: /**
203: * Start the thread. Assume the current state is <code>STOPPED</code>.
204: */
205: private void startThread() {
206: checkedTransition(STOPPED, RUNNING);
207: fCurrentTimeoutState = new Timeout(false);
208: fCurrentThread = new Thread() {
209: /**
210: * Exception thrown when a thread notices that it has been stopped
211: * and a new thread has been started.
212: */
213: final class ThreadChangedException extends Exception {
214: private static final long serialVersionUID = 1L;
215: }
216:
217: /*
218: * @see java.lang.Runnable#run()
219: */
220: public void run() {
221: try {
222: run2();
223: } catch (InterruptedException e) {
224: // ignore and end the thread - we never interrupt ourselves,
225: // so it must be an external entity that interrupted us
226: Logger.global.log(Level.FINE, "", e); //$NON-NLS-1$
227: } catch (ThreadChangedException e) {
228: // the thread was stopped and restarted before we got out
229: // of a wait - we're no longer used
230: // we might have been notified instead of the current thread,
231: // so wake it up
232: Logger.global.log(Level.FINE, "", e); //$NON-NLS-1$
233: synchronized (fMutex) {
234: fMutex.notifyAll();
235: }
236: }
237: }
238:
239: /**
240: * Runs the thread.
241: *
242: * @throws InterruptedException if the thread was interrupted
243: * @throws ThreadChangedException if the thread changed
244: */
245: private void run2() throws InterruptedException,
246: ThreadChangedException {
247: synchronized (fMutex) {
248: checkThread();
249: tryHold(); // wait / potential state change
250: assertStates(STOPPED | RUNNING);
251:
252: while (isState(RUNNING)) {
253: waitForTimeout(); // wait / potential state change
254:
255: if (isState(RUNNING))
256: timedOut(); // state change
257: assertStates(STOPPED | IDLE);
258:
259: tryHold(); // wait / potential state change
260: assertStates(STOPPED | RUNNING);
261: }
262: assertStates(STOPPED);
263: }
264: }
265:
266: /**
267: * Check whether the current thread is this thread, throw an
268: * exception otherwise.
269: *
270: * @throws ThreadChangedException if the current thread changed
271: */
272: private void checkThread() throws ThreadChangedException {
273: if (fCurrentThread != this )
274: throw new ThreadChangedException();
275: }
276:
277: /**
278: * Waits until the next timeout occurs.
279: *
280: * @throws InterruptedException if the thread was interrupted
281: * @throws ThreadChangedException if the thread changed
282: */
283: private void waitForTimeout() throws InterruptedException,
284: ThreadChangedException {
285: long delta;
286: while (isState(RUNNING)
287: && (delta = fNextTimeout
288: - System.currentTimeMillis()) > 0) {
289: delta = Math.max(delta, 50); // wait at least 50ms in order to avoid timing out before the display is going to sleep
290: Logger.global
291: .finest("sleeping for " + delta + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
292: fMutex.wait(delta);
293: checkThread();
294: }
295: }
296:
297: /**
298: * Sets the timed out flag and wakes up the display. Transitions to
299: * <code>IDLE</code> (if in keep-running mode) or
300: * <code>STOPPED</code>.
301: */
302: private void timedOut() {
303: Logger.global.finer("timed out"); //$NON-NLS-1$
304: fCurrentTimeoutState.setTimedOut(true);
305: fDisplay.wake(); // wake up call!
306: if (fKeepRunningOnTimeout)
307: checkedTransition(RUNNING, IDLE);
308: else
309: checkedTransition(RUNNING, STOPPED);
310: }
311:
312: /**
313: * Waits while the state is <code>IDLE</code>, then returns. The
314: * state must not be <code>RUNNING</code> when calling this
315: * method. The state is either <code>STOPPED</code> or
316: * <code>RUNNING</code> when the method returns.
317: *
318: * @throws InterruptedException if the thread was interrupted
319: * @throws ThreadChangedException if the thread has changed while on
320: * hold
321: */
322: private void tryHold() throws InterruptedException,
323: ThreadChangedException {
324: while (isState(IDLE)) {
325: fMutex.wait(0);
326: checkThread();
327: }
328: assertStates(STOPPED | RUNNING);
329: }
330: };
331:
332: fCurrentThread.start();
333: }
334:
335: /**
336: * Transitions to <code>nextState</code> if the current state is one of
337: * <code>possibleStates</code>. Returns <code>true</code> if the
338: * transition happened, <code>false</code> otherwise.
339: *
340: * @param possibleStates the states which trigger a transition
341: * @param nextState the state to transition to
342: * @return <code>true</code> if the transition happened,
343: * <code>false</code> otherwise
344: */
345: private boolean tryTransition(int possibleStates, int nextState) {
346: if (isState(possibleStates)) {
347: Logger.global
348: .finer(name(fState)
349: + " > " + name(nextState) + " (" + name(possibleStates) + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
350: fState = nextState;
351: return true;
352: }
353: Logger.global
354: .finest("noTransition" + name(fState) + " !> " + name(nextState) + " (" + name(possibleStates) + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
355: return false;
356: }
357:
358: /**
359: * Checks the <code>possibleStates</code> and throws an assertion if it is
360: * not met, then transitions to <code>nextState</code>.
361: *
362: * @param possibleStates the allowed states
363: * @param nextState the state to transition to
364: */
365: private void checkedTransition(int possibleStates, int nextState) {
366: assertStates(possibleStates);
367: Logger.global.finer(name(fState) + " > " + name(nextState)); //$NON-NLS-1$
368: fState = nextState;
369: }
370:
371: /**
372: * Implements state consistency checking.
373: *
374: * @param states the allowed states
375: * @throws junit.framework.AssertionFailedError if the current state is not
376: * in <code>states</code>
377: */
378: private void assertStates(int states) {
379: Assert.assertTrue("illegal state", isState(states)); //$NON-NLS-1$
380: }
381:
382: /**
383: * Answers <code>true</code> if the current state is in the given
384: * <code>states</code>.
385: *
386: * @param states the possible states
387: * @return <code>true</code> if the current state is in the given states,
388: * <code>false</code> otherwise
389: */
390: private boolean isState(int states) {
391: return (states & fState) == fState;
392: }
393:
394: /**
395: * Pretty print the given states.
396: *
397: * @param states the states
398: * @return a string representation of the states
399: */
400: private String name(int states) {
401: StringBuffer buf = new StringBuffer();
402: boolean comma = false;
403: if ((states & RUNNING) == RUNNING) {
404: buf.append("RUNNING"); //$NON-NLS-1$
405: comma = true;
406: }
407: if ((states & STOPPED) == STOPPED) {
408: if (comma)
409: buf.append(","); //$NON-NLS-1$
410: buf.append("STOPPED"); //$NON-NLS-1$
411: comma = true;
412: }
413: if ((states & IDLE) == IDLE) {
414: if (comma)
415: buf.append(","); //$NON-NLS-1$
416: buf.append("IDLE"); //$NON-NLS-1$
417: }
418: return buf.toString();
419: }
420:
421: }
|