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 }