001: /*
002: * Copyright 2007 Hippo.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package nl.hippo.cms.brokenlinkchecker.threading;
017:
018: /**
019: * <p>
020: * A background task that should run until interrupted or until a specific
021: * condition occurs. The task will perform work and then sleep for new work to
022: * arrive.
023: * </p>
024: */
025: public abstract class Task implements Runnable {
026: /**
027: * <p>
028: * The default number of milliseconds that will be slept between two
029: * sets of work.
030: * </p>
031: */
032: private static final long DEFAULT_TIME_TO_SLEEP_BETWEEN_WORK_MILLIS = 1000L;
033:
034: /**
035: * <p>
036: * The default number of milliseconds to wait between checks to see if
037: * the task has stopped.
038: * </p>
039: */
040: private static final long DEFAULT_TIME_TO_SLEEP_WHILE_WAITING_UNTIL_STOPPED_MILLIS = 1000L;
041:
042: /**
043: * <p>
044: * The thread on which this task is running. If this attribute is
045: * <code>null</code> the task is not running.
046: * </p>
047: */
048: private Thread taskThread;
049:
050: /**
051: * <p>
052: * Create an instance of this task.
053: * </p>
054: */
055: protected Task() {
056: super ();
057:
058: // No action needed. There is nothing to initialize.
059: }
060:
061: /**
062: * <p>
063: * Run the task. The task will perform work, sleep and then start over
064: * until its thread is interrupted or the task implementation determines
065: * there is no more work to be done.
066: * </p>
067: */
068: public final void run() {
069: determineThreadOfTask();
070:
071: try {
072: while (isAllowedToRun()) {
073: performWork();
074:
075: sleep(getTimeToSleepBetweenWorkMillis());
076: }
077: } finally {
078: clearThreadOfTask();
079: cleanUp();
080: }
081: }
082:
083: /**
084: * <p>
085: * Tell this task to stop performing its work.
086: * </p>
087: */
088: public synchronized void stop() {
089: taskThread.interrupt();
090: }
091:
092: /**
093: * <p>
094: * Wait forever (or until interrupted) for this task to stop completely.
095: * </p>
096: */
097: public void waitUntilStopped() {
098: waitUntilStoppedOrUntil(Long.MAX_VALUE);
099: }
100:
101: /**
102: * <p>
103: * Wait for at most a specified amount of milliseconds for this task to
104: * stop completely.
105: * </p>
106: *
107: * @param waitTimeMillis
108: * the maximum amount of milliseconds to wait.
109: */
110: public void waitUntilStoppedFor(long waitTimeMillis) {
111: long currentTimeMillis = System.currentTimeMillis();
112:
113: long waitLimitMillis = currentTimeMillis + waitTimeMillis;
114: // Detect and handle overflow
115: if (waitLimitMillis < currentTimeMillis) {
116: waitLimitMillis = Long.MAX_VALUE;
117: }
118:
119: waitUntilStoppedOrUntil(waitLimitMillis);
120: }
121:
122: /**
123: * <p>
124: * Wait until at most a specified time for this task to stop completely.
125: * </p>
126: *
127: * @param waitLimitMillis
128: * the time when the stop waiting.
129: */
130: public void waitUntilStoppedOrUntil(long waitLimitMillis) {
131: try {
132: while (isRunning()
133: && (System.currentTimeMillis() < waitLimitMillis)) {
134: Thread
135: .sleep(getTimeToSleepWhileWaitingUntilStoppedMillis());
136: }
137: } catch (InterruptedException e) {
138: Thread.currentThread().interrupt();
139: }
140: }
141:
142: /**
143: * <p>
144: * Perform the actual work of this task. The implementation should
145: * perform as much work as it can and then return.
146: * </p>
147: */
148: protected abstract void performWork();
149:
150: /**
151: * <p>
152: * Determine whether or not this task should stop based on an
153: * implementation-specific stop condition.
154: * </p>
155: *
156: * @return <code>true</code> if this task should stop,
157: * <code>false</code> otherwise.
158: */
159: protected boolean hasStopConditionBeenMet() {
160: // By default the task does not stop until interrupted.
161: return false;
162: }
163:
164: /**
165: * <p>
166: * Perform cleanup when this task stops. This method is called from a
167: * <code>finally</code> clause so should not throw any (runtime)
168: * exceptions, so the original exception is not swallowed in case of
169: * abnormal termination of the <code>try</code> clause.
170: * </p>
171: */
172: protected void cleanUp() {
173: // No action needed. By default nothing needs to be cleaned up.
174: }
175:
176: /**
177: * <p>
178: * The number of milliseconds to sleep between performing work. While
179: * this task is sleeping new work can arrive which will be processed as
180: * soon as this task awakes.
181: * </p>
182: *
183: * @return the number of milliseconds to sleep between performing work.
184: */
185: protected long getTimeToSleepBetweenWorkMillis() {
186: return DEFAULT_TIME_TO_SLEEP_BETWEEN_WORK_MILLIS;
187: }
188:
189: /**
190: * <p>
191: * The number of milliseconds to sleep between checks to see if this
192: * task has stopped.
193: * </p>
194: *
195: * @return the number of milliseconds to sleep between checks.
196: */
197: protected long getTimeToSleepWhileWaitingUntilStoppedMillis() {
198: return DEFAULT_TIME_TO_SLEEP_WHILE_WAITING_UNTIL_STOPPED_MILLIS;
199: }
200:
201: /**
202: * <p>
203: * Determine the thread that this task is running on. The thread of this
204: * task is needed to get and set the task interruption status.
205: * </p>
206: */
207: private synchronized void determineThreadOfTask() {
208: taskThread = Thread.currentThread();
209: }
210:
211: /**
212: * <p>
213: * Set the thread this task is running on to <code>null</code>. This
214: * effectively changes the state of this task to <i>not running</i>.
215: * </p>
216: */
217: private synchronized void clearThreadOfTask() {
218: taskThread = null;
219: }
220:
221: /**
222: * <p>
223: * Determine whether or not this task is running.
224: * </p>
225: *
226: * @return <code>true</code> if this task is currently running,
227: * <code>false</code> otherwise.
228: */
229: private synchronized boolean isRunning() {
230: return taskThread != null;
231: }
232:
233: /**
234: * <p>
235: * Determine whether or not this task is allowed to run.
236: * </p>
237: *
238: * <p>
239: * <strong>Warning</strong>: this method should only be invoked from
240: * the thread this task is running on.
241: * </p>
242: *
243: * @return <code>true</code> if this task is allowed to perform work,
244: * <code>false</code> if this task should stop.
245: */
246: private boolean isAllowedToRun() {
247: return !hasTaskThreadBeenInterrupted()
248: && !hasStopConditionBeenMet();
249: }
250:
251: /**
252: * <p>
253: * Determine whether or not the task this thread is running on has been
254: * interrupted.
255: * </p>
256: *
257: * @return <code>true</code> if the thread this task is running on has
258: * been interrupted, <code>false</code> otherwise.
259: */
260: private boolean hasTaskThreadBeenInterrupted() {
261: return taskThread.isInterrupted();
262: }
263:
264: /**
265: * <p>
266: * Put this task to sleep for a number of milliseconds.
267: * </p>
268: *
269: * <p>
270: * <strong>Warning</strong>: this method should only be invoked from
271: * the thread this task is running on.
272: * </p>
273: *
274: * @param timeToSleepMillis
275: * the number of milliseconds to sleep.
276: */
277: private void sleep(long timeToSleepMillis) {
278: try {
279: Thread.sleep(timeToSleepMillis);
280: } catch (InterruptedException e) {
281: taskThread.interrupt();
282: }
283: }
284: }
|