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  }