1 /* 2 * Copyright the original author or authors. 3 * 4 * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.mozilla.org/MPL/MPL-1.1.html 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 import org.as2lib.env.except.AbstractOperationException; 18 import org.as2lib.env.except.IllegalArgumentException; 19 import org.as2lib.app.exec.Process; 20 import org.as2lib.app.exec.Executable; 21 import org.as2lib.app.exec.ProcessErrorListener; 22 import org.as2lib.app.exec.ProcessFinishListener; 23 import org.as2lib.app.exec.ProcessStartListener; 24 import org.as2lib.app.exec.ProcessPauseListener; 25 import org.as2lib.app.exec.ProcessResumeListener; 26 import org.as2lib.app.exec.ProcessUpdateListener; 27 import org.as2lib.app.exec.AbstractTimeConsumer; 28 import org.as2lib.data.holder.Map; 29 import org.as2lib.data.holder.map.HashMap; 30 import org.as2lib.util.MethodUtil; 31 import org.as2lib.env.event.distributor.CompositeEventDistributorControl; 32 33 /** 34 * {@code AbstractProcess} is a abstract helper class to implement processes. 35 * 36 * <p>Most of the functionalities of {@link Process} are served well within 37 * {@code AbstractProcess}. Because of the situation that most processes are 38 * simple executions {@code AbstractProcess} allows easy implementations of 39 * {@link Process}. 40 * 41 * <p>To use the advantage of {@code AbstractProcess} simple extend it and 42 * implement the {@link #run} method. 43 * 44 * <p>It executes {@link #run} at {@link #start} and handles the exeuction of 45 * events and the correct states. If you wan't to stop your process 46 * {@link #pause} and {@link #resume} offer direct support. 47 * 48 * <p>Example for a process that can not be finished by return: 49 * <code> 50 * class MyProcess extends AbstractProcess { 51 * 52 * public function run() { 53 * // Xml that has to work 54 * var xml:Xml = new Xml(); 55 * 56 * // helper for the call back. 57 * var inst = this; 58 * 59 * // Mtasc compatible return from loading 60 * xml.onLoad = function() { 61 * inst["finish"](); 62 * } 63 * 64 * // Flag to not finish automatically 65 * working = true; 66 * 67 * // Start of loading the xml file. 68 * xml.load("test.xml"); 69 * } 70 * } 71 * </code> 72 * 73 * @author Martin Heidegger 74 * @version 1.0 75 * @see Process 76 * @see ProcessStartListener 77 * @see ProcessFinishListener 78 * @see ProcessErrorListener 79 * @see ProcessPauseListener 80 * @see ProcessResumeListener 81 * @see ProcessUpdateListener 82 */ 83 class org.as2lib.app.exec.AbstractProcess extends AbstractTimeConsumer 84 implements Process, 85 ProcessErrorListener, 86 ProcessFinishListener { 87 88 /** Flag if execution was paused. */ 89 private var paused:Boolean; 90 91 /** Flag if execution is just not working (case if a event is not finished within .start). */ 92 private var working:Boolean; 93 94 /** List of all errors that occur during execution. */ 95 private var errors:Array; 96 97 /** All subprocesses (Key) and its mapped callBacks (Value). */ 98 private var subProcesses:Map; 99 100 /** Contains the possible parent. */ 101 private var parent:Process; 102 103 /** Shorter name for the concrete distributorControl. */ 104 private var dC:CompositeEventDistributorControl; 105 106 /** 107 * Constructs a new {@code AbstractProcess}. 108 */ 109 private function AbstractProcess(Void) { 110 dC = distributorControl; 111 acceptListenerType(ProcessStartListener); 112 acceptListenerType(ProcessErrorListener); 113 acceptListenerType(ProcessUpdateListener); 114 acceptListenerType(ProcessPauseListener); 115 acceptListenerType(ProcessResumeListener); 116 acceptListenerType(ProcessFinishListener); 117 errors = new Array(); 118 subProcesses = new HashMap(); 119 paused = false; 120 working = false; 121 } 122 123 /** 124 * Setter for a parent process. 125 * 126 * <p>Validates if the passed-in {@code Process} is this instance or has 127 * this instance in its parent hierarchy (to prevent recursions). 128 * 129 * @param p {@code Process} to act as parent 130 * @throws IllegalArgumentException if the applied process has the current 131 * instance in its parent hierarchy or it is the current instance 132 */ 133 public function setParentProcess(p:Process):Void { 134 do { 135 if(p == this) { 136 throw new IllegalArgumentException("You can not start a process with itself as super process.", this, arguments); 137 } 138 } while (p = p.getParentProcess()); 139 parent = p; 140 } 141 142 /** 143 * Returns the {@code Process} that acts as parent for this process. 144 * 145 * @return parent {@code Process} if set, else {@code null} 146 */ 147 public function getParentProcess(Void):Process { 148 return parent; 149 } 150 151 /** 152 * Starts a sub-process. 153 * 154 * <p>This method allows to start a {@code Process}. It registers itself as 155 * parent of the passed-in {@code process} and starts the {@code process} 156 * if necessary. 157 * 158 * <p>If you add a sub-process it will be started immediately. This is important 159 * if you start more than one sub-process, because they won't get executed in 160 * a row like its handled within a {@link org.as2lib.app.exec.Batch}. 161 * 162 * <p>This process will not be finished until all subprocesses have finished. 163 * 164 * <p>On finish of the {@code process} to start it will execute the passed-in 165 * {@code callBack}. 166 * 167 * @param process process to be used as sub-process 168 * @param args arguments for the process start 169 * @param callBack call back to be executed if the process finishes 170 */ 171 public function startSubProcess(process:Process, args:Array, callBack:Executable):Void { 172 // Don't do anything if the the process is already registered as sub-process. 173 if (!subProcesses.containsKey(process)) { 174 process.addListener(this); 175 process.setParentProcess(this); 176 subProcesses.put(process, callBack); 177 if (!isPaused()) { 178 pause(); 179 } 180 // Start if not started. 181 // Re-start if finished. 182 // Do nothing if started but not finished. 183 if(!process.hasStarted() || process.hasFinished()) { 184 process["start"].apply(process, args); 185 } 186 } 187 } 188 189 /** 190 * Pauses the process. 191 */ 192 public function pause(Void):Void { 193 paused = true; 194 sendPauseEvent(); 195 } 196 197 /** 198 * Resumes the process. 199 */ 200 public function resume(Void):Void { 201 paused = false; 202 sendResumeEvent(); 203 } 204 205 /** 206 * Prepares the start of the process. 207 */ 208 private function prepare(Void):Void { 209 started = false; 210 paused = false; 211 finished = false; 212 working = false; 213 totalTime.setValue(0); 214 restTime.setValue(0); 215 sendStartEvent(); 216 started = true; 217 } 218 219 /** 220 * Starts the process. 221 * 222 * <p>Any applied parameter will be passed to the {@link #run} implementation. 223 * 224 * @return (optional) result of {@code run()} 225 */ 226 public function start() { 227 prepare(); 228 var result; 229 try { 230 delete endTime; 231 startTime = getTimer(); 232 result = MethodUtil.invoke("run", this, arguments); 233 } catch(e) { 234 sendErrorEvent(e); 235 } 236 if(!isPaused() && !isWorking()) { 237 finish(); 238 } 239 return result; 240 } 241 242 /** 243 * Flag if the current implementation is working. 244 * 245 * <p>Important for {@link #start} this method indicates that the just starting 246 * process is not finished by return. 247 * 248 * @return {@code true} if the implementation is working 249 */ 250 private function isWorking(Void):Boolean { 251 return working; 252 } 253 254 /** 255 * Template method for running the process. 256 * 257 * @throws AbstractOperationException if the method was not extended 258 */ 259 private function run(Void):Void { 260 throw new AbstractOperationException(".run is abstract and has to be implemented.", this, arguments); 261 } 262 263 /** 264 * Returns {@code true} if the process is paused. 265 * 266 * @return {@code true} if the process is paused 267 */ 268 public function isPaused(Void):Boolean { 269 return paused; 270 } 271 272 273 /** 274 * Returns {@code true} if the process is running. 275 * 276 * @return {@code true} if the process is running 277 */ 278 public function isRunning(Void):Boolean { 279 return(!isPaused() && hasStarted()); 280 } 281 282 /** 283 * Method to be executed if a process finishes its execution. 284 * 285 * @param process {@link Process} that finished with execution 286 */ 287 public function onProcessFinish(process:Process):Void { 288 if (subProcesses.containsKey(process)) { 289 // removes current as listener 290 process.removeListener(this); 291 // Remove the process and executes the registered callback. 292 subProcesses.remove(process).execute(); 293 // Resume exeuction 294 resume(); 295 } 296 } 297 298 /** 299 * Method to be executed if a exception was thrown during the execution. 300 * 301 * @param process {@link Process} where a error occured 302 * @param error error that occured 303 * @return {@code true} if error was consumed 304 */ 305 public function onProcessError(process:Process, error):Boolean { 306 return sendErrorEvent(error); 307 } 308 309 /** 310 * Internal Method to finish the execution. 311 */ 312 private function finish(Void):Void { 313 if (subProcesses.isEmpty() && isRunning()) { 314 finished = true; 315 started = false; 316 working = false; 317 endTime = getTimer(); 318 sendFinishEvent(); 319 } 320 } 321 322 /** 323 * Returns {@code true} if a error occured. 324 * 325 * @return {@code true} if a error occured 326 */ 327 public function hasError(Void):Boolean { 328 return (getErrors().length != 0); 329 } 330 331 /** 332 * Returns the list with all occured errors. 333 * 334 * @return list that contains all occured errors 335 */ 336 public function getErrors(Void):Array { 337 return errors; 338 } 339 340 /** 341 * Stores the {@code error} in the list of occured errors and finishes the process. 342 * 343 * <p>It will set the error to -1 if you interrupt without a error. 344 * 345 * @param error error that occured to interrupt 346 */ 347 private function interrupt(error):Void { 348 publishError(error); 349 finish(); 350 } 351 352 /** 353 * Publishes the error event and stores the error in the error list. 354 * 355 * @param error error to be published 356 * @return {@code true} if event was consumed 357 */ 358 private function publishError(error):Boolean { 359 if (!error) error = -1; 360 this.errors.push(error); 361 return sendErrorEvent(error); 362 } 363 364 /** 365 * Internal method to send update events for {@link ProcessUpdateListener}. 366 */ 367 private function sendUpdateEvent(Void):Void { 368 var updateDistributor:ProcessUpdateListener 369 = dC.getDistributor(ProcessUpdateListener); 370 updateDistributor.onProcessUpdate(this); 371 } 372 373 /** 374 * Internal method to send pause events for {@link ProcessPauseListener}. 375 */ 376 private function sendPauseEvent(Void):Void { 377 var pauseDistributor:ProcessPauseListener 378 = dC.getDistributor(ProcessPauseListener); 379 pauseDistributor.onProcessPause(this); 380 } 381 382 /** 383 * Internal method to send resume events for {@link ProcessResumeListener}. 384 */ 385 private function sendResumeEvent(Void):Void { 386 var resumeDistributor:ProcessResumeListener 387 = dC.getDistributor(ProcessResumeListener); 388 resumeDistributor.onProcessResume(this); 389 } 390 391 /** 392 * Internal method to send start events for {@link ProcessStartListener}. 393 */ 394 private function sendStartEvent(Void):Void { 395 var startDistributor:ProcessStartListener 396 = dC.getDistributor(ProcessStartListener); 397 startDistributor.onProcessStart(this); 398 } 399 400 /** 401 * Internal method to send error events for {@link ProcessErrorListener}. 402 */ 403 private function sendErrorEvent(error):Boolean { 404 var errorDistributor:ProcessErrorListener 405 = dC.getDistributor(ProcessErrorListener); 406 return errorDistributor.onProcessError(this, error); 407 } 408 409 /** 410 * Internal method to send finish events for {@link ProcessFinishListener}. 411 */ 412 private function sendFinishEvent(Void):Void { 413 var finishDistributor:ProcessFinishListener 414 = dC.getDistributor(ProcessFinishListener); 415 finishDistributor.onProcessFinish(this); 416 } 417 418 }