added a few more features from c++ implementation, mostly periodicity.
[feisty_meow.git] / kona / src / org / feistymeow / process / ethread.java
1 package org.feistymeow.process;
2
3 import org.apache.commons.logging.Log;
4 import org.apache.commons.logging.LogFactory;
5
6 /**
7  * A simple java thread that hearkens back to the HOOPLE C++ ethread in features.
8  * 
9  * @author Chris Koeritz
10  * @copyright Copyright (c) 2010-$now By Feisty Meow Concerns Ltd.
11  * @license This file is free software; you can modify and redistribute it under the terms of the
12  *          Apache License v2.0: http://www.apache.org/licenses/LICENSE-2.0
13  */
14 public abstract class ethread implements Runnable
15 {
16         private static Log c_logger = LogFactory.getLog(ethread.class);
17
18         // the actual java thread object.
19         private volatile Thread c_RealThread = null;
20         // provides synchronization for the thread.
21         private volatile Object c_lock = new Object();
22         // this is the timing period, for a timed thread. if zero, then this is a single shot thread.
23         private long c_period = 0;
24         // records whether the thread should shut down or not.
25         private boolean c_stopThread = false;
26         // snooze between checks on the stop timer.
27         final long SNOOZE_PERIOD = 20;
28
29         /**
30          * creates a new single-shot ethread without starting it. this type of thread will run just
31          * once.
32          */
33         public ethread()
34         {
35         }
36
37         /**
38          * creates a new periodic ethread without starting it. this type of thread runs every "period"
39          * milliseconds until stopped or until the performActivity method returns false.
40          */
41         public ethread(long period)
42         {
43                 c_period = period;
44         }
45
46         /**
47          * this is the main function that derived classes must implement. it does the actual work that
48          * the thread is intended to perform. note that the derived version must not do anything to
49          * cause the thread to be ripped out while performActivity is still being invoked. the return
50          * value should be true if the thread can continue executing. this is meaningless for single
51          * shot threads executed via runOnce, but matters for the periodic threads started with
52          * runPeriodic.
53          */
54         abstract boolean performActivity();
55
56         /**
57          * Begins execution of the thread.
58          */
59         public void start()
60         {
61                 synchronized (c_lock) {
62                         if (null == this.c_RealThread) {
63                                 this.c_RealThread = new Thread(this);
64                                 c_logger.debug("starting thread " + c_RealThread.getId());
65                                 this.c_RealThread.start();
66                         }
67                 }
68         }
69
70         /**
71          * Stops execution of the thread, or at least attempts to.
72          */
73         public void stop()
74         {
75                 cancel();
76                 while (true) {
77                         if (threadRunning()) {
78                                 try {
79                                         Thread.sleep(40);
80                                 } catch (InterruptedException e) {
81                                         // ignoring this since we'll keep snoozing as needed.
82                                 }
83                         } else {
84                                 break;
85                         }
86                 }
87         }
88
89         /**
90          * Signals the thread to stop executing, but does not wait for it.
91          */
92         void cancel()
93         {
94                 synchronized (c_lock) {
95                         c_stopThread = true;
96                         Thread goAway = c_RealThread;
97                         c_RealThread = null;
98                         if (null != goAway) {
99                                 goAway.interrupt();
100                         }
101                 }
102         }
103
104         /**
105          * Returns true if the thread isn't null.
106          */
107         public boolean threadRunning()
108         {
109                 synchronized (c_lock) {
110                         return this.c_RealThread != null;
111                 }
112         }
113
114         /**
115          * returns true if the thread has been told to stop running.
116          */
117         public boolean shouldStop()
118         {
119                 synchronized (c_lock) {
120                         return c_stopThread;
121                 }
122         }
123
124         /**
125          * this is the override from Runnable that allows us to call our own performActivity method.
126          * implementors should not override this; they should override performActivity instead.
127          */
128         @Override
129         public void run()
130         {
131                         if (!threadRunning()) {
132                                 return; // stopped before it ever started. how can this be? we just got invoked.
133                         }
134                         try {
135                                 while (true) {
136                                         boolean keepGoing = performActivity();
137                                         if (!keepGoing) {
138                                                 c_logger.debug("thread returned false for single shot thread.  just saying.");
139                                         }
140                                         if (c_period == 0) {
141                                                 // not a periodic thread, so we're done now.
142                                                 break;
143                                         }
144                                         long nextRun = System.currentTimeMillis() + c_period;
145                                         while (System.currentTimeMillis() < nextRun) {
146                                                 if (shouldStop()) {
147                                                         break;
148                                                 }
149                                                 try {
150                                                         Thread.sleep(SNOOZE_PERIOD);
151                                                 } catch (InterruptedException e) {
152                                                         // well, we'll hit it again soon.
153                                                 }
154                                         }
155                                 }
156                         } catch (Throwable t) {
157                                 c_logger.info("exception thrown from performActivity: " + t.getLocalizedMessage(), t);
158                         }
159                 }
160         
161 }
162