1 /*
2 * To change this template, choose Tools | Templates
3 * and open the template in the editor.
4 */
5
6 package cz.cuni.amis.utils;
7
8 import java.util.concurrent.TimeUnit;
9
10 import cz.cuni.amis.utils.flag.Flag;
11 import cz.cuni.amis.utils.flag.ImmutableFlag;
12 import cz.cuni.amis.utils.flag.WaitForFlagChange;
13
14 /**
15 * This Job class represents and wraps one job you want to execute asynchronously. It is
16 * a combination of the java.lang.Thread and java.util.concurrent.Future class from java.concurrency packaga
17 * <p><p>
18 * Usage:
19 * <ol>
20 * <li>Create your own job (anonymous class, inheritance, whatever)</li>
21 * <li>Implement job() method - there you might need to use setResult() method to specify the result of the job</li>
22 * <li>Start the job using startJob() method</li>
23 * <li>Then the thread is launched and job is being crunched</li>
24 * <li>Use await() / isFinished() / isFinishedOk() / isException() / getResult() to examine the results</li>
25 * </ol>
26 * Note that getResult() should be used only IFF isFinishedOk(), also notice that one instance of job
27 * can't be started twice (even if isFinished(), the second startJob() will throw exception)!
28 * <p>/p>
29 * Thread-safe.
30 * <p><p>
31 * Example:<p>
32 * MyJob myJob = new MyJob().startJob("my cool thread name");<p>
33 * myJob.await();<p>
34 * if (myJob.isFinishedOk()) {<p>
35 * switch(myJob.getResult()) {<p>
36 * // examine the result, act accordinally<p>
37 * }<p>
38 * }<p>
39 *
40 * @author Jimmy
41 */
42 public abstract class Job<RESULT> {
43
44 /**
45 * Exception that is thrown if you attempt to start one job twice.
46 * @author Jimmy
47 */
48 @SuppressWarnings("serial")
49 public static class JobWasAlreadyStartedException extends RuntimeException {
50
51 public JobWasAlreadyStartedException(String text) {
52 super(text);
53 }
54
55 }
56
57 /**
58 * Stores the result from the last setResult() call
59 */
60 private RESULT jobResult = null;
61
62 /**
63 * Thrown exception by the job() (if any).
64 */
65 private Exception thrownException = null;
66
67 /**
68 * Thread the job is using ... if == null the job was never started before.
69 */
70 private Thread thread = null;
71
72 /**
73 * Mutex for the whole class, access synchronization to all Job's fields.
74 */
75 private Object mutex = new Object();
76
77 /**
78 * Flag that tells you whether the job is running.
79 */
80 private Flag<Boolean> running = new Flag<Boolean>(false);
81
82 /**
83 * Returns object we used as a mutex for this class.
84 * <p><p>
85 * Do not use for your own jobs! If you screw up it will result in deadlock.
86 *
87 * @return
88 */
89 protected Object getMutex() {
90 return mutex;
91 }
92
93 /**
94 * If job is running (thread is not null and isAlive()) - interrupts the thread.
95 */
96 public void interrupt() {
97 synchronized(mutex) {
98 if (thread != null && thread.isAlive()) thread.interrupt();
99 }
100 }
101
102 /**
103 * If thread is null: returns false
104 * <p>
105 * If thread is NOT null: returns thread.isInterrupted().
106 * @return
107 */
108 public boolean isInterrupted() {
109 synchronized(mutex) {
110 if (thread != null) return thread.isInterrupted();
111 else return false;
112 }
113 }
114
115 /**
116 * Returns you a flag that is marking whether the job is running or not.
117 * @return
118 */
119 public ImmutableFlag<Boolean> getRunningFlag() {
120 synchronized(mutex) {
121 return running.getImmutable();
122 }
123 }
124
125 /**
126 * Immediately tells you whether the job is running.
127 * @return
128 */
129 public boolean isRunning() {
130 synchronized(mutex) {
131 return running.getFlag();
132 }
133 }
134
135 /**
136 * Tells you whether the job has ended ... it doesn't tell you whether it finished OK or KO,
137 * use isException() to ask whether the job has finished OK.
138 * @return
139 */
140 public boolean isFinished() {
141 synchronized(mutex) {
142 return isStarted() && !isRunning();
143 }
144 }
145
146 /**
147 * True means: the job has finished correctly without throwing any exception...<p>
148 * False means: job has not finished yet / was not even started / or exception has occured.
149 * @return
150 */
151 public boolean isFinishedOk() {
152 synchronized(mutex) {
153 return isFinished() && !isException();
154 }
155 }
156
157 /**
158 * Whether the job was already (somewhere in the past) started. <p>
159 * <b>If returns true it does not necessarily mean the job is running!</b>
160 * @return
161 */
162 public boolean isStarted() {
163 synchronized(mutex) {
164 return thread != null;
165 }
166 }
167
168 /**
169 * Use this protected method to set the result of the job. (Default result is null.)
170 * @param result
171 */
172 protected void setResult(RESULT result) {
173 synchronized(mutex) {
174 jobResult = result;
175 }
176 }
177
178 /**
179 * Returns job result - should be used
180 * @return
181 */
182 public RESULT getResult() {
183 synchronized(mutex) {
184 return jobResult;
185 }
186 }
187
188 /**
189 * Whether the exception occurred during the job().
190 * @return
191 */
192 public boolean isException() {
193 synchronized(mutex) {
194 return thrownException != null;
195 }
196 }
197
198 /**
199 * If isException() this returns an exception that has occured.
200 * @return
201 */
202 public Exception getException() {
203 synchronized(mutex) {
204 return thrownException;
205 }
206 }
207
208 /**
209 * Do your job here.
210 */
211 protected abstract void job() throws Exception;
212
213 /**
214 * Wraps the call of the job() method - instantiated inside startJob().
215 * @author Jimmy
216 */
217 private class RunJob implements Runnable {
218
219 /**
220 * Calls the job() method from the Job class, correctly sets running flag / thrownException.
221 */
222 public void run() {
223 try {
224 job();
225 } catch (Exception e) {
226 synchronized(mutex) {
227 thrownException = e;
228 }
229 } finally {
230 synchronized(mutex) {
231 running.setFlag(false);
232 }
233 }
234 }
235 }
236
237 /**
238 * If isRunning(), this will await till the job finishes.
239 * @throws InterruptedException
240 */
241 public void await() throws InterruptedException {
242 new WaitForFlagChange<Boolean>(running, false).await();
243 }
244
245 /**
246 * If isRunning(), this will await till the job finishes (with specified timeout).
247 * @throws InterruptedException
248 * @return {@code true} if the count reached zero and {@code false}
249 * if the waiting time elapsed before the count reached zero
250 */
251 public boolean await(long timeoutMillis) throws InterruptedException {
252 return new WaitForFlagChange<Boolean>(running, false).await(timeoutMillis, TimeUnit.MILLISECONDS);
253 }
254
255 /**
256 * Starts the job (only iff !isStarted()) in the new thread.
257 * <p><p>
258 * If isStarted() ... JobWasAlreadyStartedException is thrown.
259 * @param job
260 * @return this instance (you may immediately call await())
261 */
262 public Job<RESULT> startJob() {
263 synchronized(mutex) {
264 return startJob("JobRunnable[" + this + "]");
265 }
266 }
267
268 /**
269 * Starts the job (only iff !isStarted()) in the new thread (with specific thread name).
270 * <p><p>
271 * If isStarted() ... JobWasAlreadyStartedException is thrown.
272 * @param threadName
273 * @return this instance (you may immediately call await())
274 */
275 public Job<RESULT> startJob(String threadName) {
276 synchronized(mutex) {
277 if (thread != null) throw new JobWasAlreadyStartedException("Job was already started... can't run one job twice.");
278 thread = new Thread(new RunJob(), threadName);
279 running.setFlag(true);
280 thread.start();
281 return this;
282 }
283 }
284
285 }