View Javadoc

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 }