001package jmri.util;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.util.Calendar;
005
006/**
007 * Common utility class for handling the "spurious wakeup from wait()" problem
008 * documented in {@link java.lang.Object#wait(long)}.
009 *
010 * Generally, when waiting for a notify() operation, you need to provide a test
011 * that a valid notify had happened due to a state change or other .
012 * <pre><code>
013 * new WaitHandler(this, 120) {
014 * protected boolean wasSpurious() {
015 * return !(state == expectedNextState);
016 * }
017 * };
018 * </code></pre>
019 *
020 * By default, interrupting the thread leaves the wait early with the
021 * interrupted flag set. InterruptedException is not thrown. You can modify this
022 * behavior via the handleInterruptedException routine.
023 *
024 * @author Bob Jacobsen Copyright 2010
025 */
026public class WaitHandler {
027
028    /**
029     * Wait for a specified interval, robustly handling "spurious wake"
030     *
031     * @param self     waiting Object
032     * @param interval in milliseconds
033     */
034    public WaitHandler(Object self, long interval) {
035        long currentTime = Calendar.getInstance().getTimeInMillis();
036        long endTime = currentTime + interval;
037
038        // loop until interrupted, or non-spurious wake
039        while (endTime > (currentTime = Calendar.getInstance().getTimeInMillis())) {
040            long wait = endTime - currentTime;
041            try {
042                synchronized (self) {
043                    self.wait(wait);
044
045                    if (!wasSpurious()) {
046                        break;
047                    }
048                }
049            } catch (InterruptedException e) {
050                Thread.currentThread().interrupt(); // retain if needed later
051                break;  // and leave the wait now
052            }
053        }
054    }
055
056    /**
057     * Wait forever, robustly handling "spurious wake"
058     *
059     * @param self waiting Object
060     */
061    @SuppressFBWarnings(value = "UW_UNCOND_WAIT", justification = "unguarded wait() used intentionally here as part of utility class")
062    public WaitHandler(Object self) {
063        // loop until interrupted, or non-spurious wake
064        while (true) {
065            try {
066                synchronized (self) {
067                    self.wait();
068
069                    if (!wasSpurious()) {
070                        break;
071                    }
072                }
073            } catch (InterruptedException e) {
074                if (handleInterruptedException(e)) {
075                    break;
076                }
077            }
078        }
079    }
080
081    /**
082     * Method to determine if a wake was spurious or not. By default, all wakes
083     * are considered not spurious and the full time may not elapse. Override to
084     * provide a test (returning true) when there's a way to tell that a wake
085     * was spurious and the wait() should continue.
086     *
087     * @return false unless overridden by a subclass
088     */
089    protected boolean wasSpurious() {
090        return false;
091    }
092
093    /**
094     * Define interrupt processing.
095     *
096     * By default, just records and leaves the wait early.
097     *
098     * @param e the exception to handle
099     * @return true if should break out of wait
100     */
101    boolean handleInterruptedException(InterruptedException e) {
102        Thread.currentThread().interrupt(); // retain if needed later
103        return true;  // and leave the wait now
104    }
105}