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.IllegalArgumentException;
    18  import org.as2lib.app.exec.Process;
    19  import org.as2lib.app.exec.BatchStartListener;
    20  import org.as2lib.app.exec.BatchFinishListener;
    21  import org.as2lib.app.exec.BatchErrorListener;
    22  import org.as2lib.app.exec.BatchUpdateListener;
    23  import org.as2lib.app.exec.ProcessErrorListener;
    24  import org.as2lib.app.exec.ProcessPauseListener;
    25  import org.as2lib.app.exec.ProcessFinishListener;
    26  import org.as2lib.app.exec.ProcessResumeListener;
    27  import org.as2lib.app.exec.ProcessUpdateListener;
    28  import org.as2lib.app.exec.ProcessStartListener;
    29  import org.as2lib.app.exec.AbstractProcess;
    30  import org.as2lib.app.exec.Batch;
    31  
    32  /**
    33   * {@code BatchProcess} is a implementation of {@link Batch} for a list of
    34   * Processes.
    35   * 
    36   * <p>A {@code BatchProcess} acts like a row execution of {@link Process}
    37   * instances. All listed processes will be executed after each other.
    38   * 
    39   * <p>As {@code BatchProcess} itself acts like a Process you can use it as a 
    40   * composite to execute a {@code BatchProcess} within another
    41   * {@code BatchProcess}.
    42   * 
    43   * <p>It supports beneath all listeners of {@link Process} seperate events for
    44   * {@code Batch} processing:
    45   *   {@link BatchStartListener}, {@link BatchFinishListener},
    46   *   {@link BatchUpdateListener} and {@link BatchErrorListener}
    47   *
    48   * @author Martin Heidegger
    49   * @version 1.0
    50   */
    51  class org.as2lib.app.exec.BatchProcess extends AbstractProcess
    52  	implements Batch,
    53  	    ProcessUpdateListener,
    54  		ProcessPauseListener,
    55  	    ProcessResumeListener,
    56  	    ProcessStartListener,
    57  		ProcessFinishListener,
    58  		ProcessErrorListener
    59  	 {
    60  	
    61  	/** List that contains all processes */
    62  	private var list:Array;
    63  	
    64  	/** Holder for the percents already executed */
    65  	private var percent:Number;
    66  	
    67  	/** Current running process */
    68  	private var current:Number;
    69  	
    70  	/**
    71  	 * Constructs a new {@code BatchProcess}
    72  	 */
    73  	public function BatchProcess(Void) {
    74  		acceptListenerType(BatchStartListener);
    75  		acceptListenerType(BatchFinishListener);
    76  		acceptListenerType(BatchErrorListener);
    77  		acceptListenerType(BatchUpdateListener);
    78  		list = new Array();
    79  		
    80  		percent = 0;
    81  		started = false;
    82  		finished = false;
    83  	}
    84  	
    85  	/**
    86  	 * Setter for the parent process
    87  	 * 
    88  	 * @throws IllegalArgumentException if the passed process has the current
    89  	 *         instance as a parent process (recursion safe).
    90  	 * @param p Process to be set as parent
    91  	 */
    92  	public function setParentProcess(p:Process):Void {
    93  		parent = p;
    94  		do {
    95  			if(p == this) {
    96  				throw new IllegalArgumentException("You can not start a process with itself as super process.", this, arguments);
    97  			}
    98  		} while (p = p.getParentProcess());
    99  	}
   100  	
   101  	/**
   102  	 * Getter for the applied parent process.
   103  	 * 
   104  	 * @return Parent process or null if no parent process has been set.
   105  	 */
   106  	public function getParentProcess(Void):Process {
   107  		return parent;
   108  	}
   109  	
   110  	/**
   111  	 * Getter for the currently execution process.
   112  	 * 
   113  	 * @return Currently processing Process.
   114  	 */
   115      public function getCurrentProcess(Void):Process {
   116  		return list[current];
   117  	}
   118  	
   119  	/**
   120  	 * Getter for a list of all added processes with {@link #addProcess} in
   121  	 * form of a array.
   122  	 * 
   123  	 * @return Array that contains all processes that has been added.
   124  	 */
   125  	public function getAllAddedProcesses(Void):Array {
   126  		return list.concat();
   127  	}
   128  	
   129  	/**
   130  	 * To be executed if one child process has been finished.
   131  	 * 
   132  	 * @param info Finished Process.
   133  	 */
   134  	public function onProcessFinish(info:Process):Void {
   135  		if (info == getCurrentProcess()) {
   136  			info.removeListener(this);
   137  			nextProcess();
   138  		} else {
   139  			var error:IllegalArgumentException = new IllegalArgumentException("Unexpected onFinishProcess occured from "+info+".", this, arguments);
   140  			publishError(error);
   141  			finish();
   142  		}
   143  	}
   144  	
   145  	/**
   146  	 * Internal helper to start the next process.
   147  	 */
   148  	private function nextProcess(Void):Void {
   149  		if (current < list.length-1) {
   150  			updatePercent(100);
   151  			current ++;
   152  			var c:Process = list[current];
   153  			c.setParentProcess(this);
   154  			c.addListener(this);
   155  			sendUpdateEvent();
   156  			c.start();
   157  		} else {
   158  			finish();
   159  		}
   160  	}
   161  	
   162  	/**
   163  	 * Void implementation of {@link ProcessStartListener#onProcessStart}
   164  	 * 
   165  	 * @param info to the process that has been started.
   166  	 */
   167  	public function onProcessStart(info:Process):Void {}
   168  	
   169  	/**
   170  	 * Implementation of {@link ProcessPauseListener#onProcessPause} that redirects
   171  	 * to internal eventbroadcasting.
   172  	 * 
   173  	 * @param info to the process that has been paused.
   174  	 */
   175  	public function onProcessPause(info:Process):Void {
   176  		if (info == getCurrentProcess()) {
   177  			sendPauseEvent();
   178  		} else {
   179  			publishError(new IllegalArgumentException("Unexpected onPauseProcess occured from "+info+". Expected was "+getCurrentProcess(), this, arguments));
   180  		}
   181  	}
   182  	
   183  	/**
   184  	 * Implementation of {@link ProcessResumeListener#onProcessResume} that redirects
   185  	 * to internal eventbroadcasting.
   186  	 * 
   187  	 * @param info to the process that has been resumed.
   188  	 */
   189  	public function onProcessResume(info:Process):Void {
   190  		if (info == getCurrentProcess()) {
   191  			sendResumeEvent();
   192  		} else {
   193  			publishError(new IllegalArgumentException("Unexpected onResumeProcess occured from "+info+".", this, arguments));
   194  		}
   195  	}
   196  	
   197  	/**
   198  	 * Implementation of {@link ProcessErrorListener#onProcessError} that redirects
   199  	 * to internal eventbroadcasting.
   200  	 * 
   201  	 * @param info to the process that has thrown the error.
   202  	 */
   203  	public function onProcessError(info:Process, error):Boolean {
   204  		var result:Boolean = false;
   205  		if (info != getCurrentProcess()) {
   206  			error = new IllegalArgumentException("Unexpected onProcessError occured from "+info+".", this, arguments);
   207  		}
   208  		
   209  		result = publishError(error);
   210  		if (!result) {
   211  			finish();
   212  		}
   213  		return result;
   214  	}
   215  	
   216  	/**
   217  	 * Implementation of {@link ProcessUpdateListener#onProcessUpdate} that redirects
   218  	 * to internal eventbroadcasting.
   219  	 * 
   220  	 * @param info to the process that got updated.
   221  	 */
   222  	public function onProcessUpdate(info:Process):Void {
   223  		var p:Number = info.getPercentage();
   224  		if(p != null) {
   225  			updatePercent(p);
   226  		}
   227  		sendUpdateEvent();
   228  	}
   229  	
   230  	/**
   231  	 * Starts the execution of the Batch.
   232  	 */
   233      public function start() {
   234      	if(!started) {
   235  			current = -1;
   236  			started = false;
   237  			finished = false;
   238  			working = true;
   239  			percent = 0;
   240  			
   241  			delete endTime;
   242  			startTime = getTimer();
   243  			sendStartEvent();
   244  			started = true;
   245  			nextProcess();
   246      	}
   247  	}
   248  	
   249  	/**
   250  	 * Adds a process to the list of processes.
   251  	 * 
   252  	 * @param p Process to be added.
   253  	 * @return Internal Id of the process.
   254  	 */
   255  	public function addProcess(p:Process):Number {
   256  		if(p != this) {
   257  			list.push(p);
   258  			updatePercent(100);
   259  			return list.length-1;
   260  		}
   261  	}
   262  	
   263  	/**
   264  	 * Removes a process from the list of executed processes.
   265  	 * 
   266  	 * <p>If a process has been added more than one times, it removes all
   267  	 * executions.
   268  	 * 
   269  	 * @param p Process to be removed.
   270  	 * @see #removeProcessById
   271  	 */
   272  	public function removeProcess(p:Process):Void {
   273  		var i:Number = list.length;
   274  		while(--i-(-1)) {
   275  			if(list[i] == p) {
   276  				list.slice(i, i);
   277  				return;
   278  			}
   279  		}
   280  	}
   281  	
   282  	/**
   283  	 * Removes a process by its internal id.
   284  	 * 
   285  	 * <p>{@link #addProcess} returns the internal id of a added process. This
   286  	 * method helps to remove concrete reference to the process.
   287  	 * 
   288  	 * @param id Internal Id of the process.
   289  	 * @see #removeProcess
   290  	 */
   291  	public function removeProcessById(id:Number):Void {
   292  		list.splice(id, 1);
   293  	}
   294  	
   295  	/**
   296  	 * Getter for the current percentage of execution.
   297  	 * 
   298  	 * @returns Amount of solved execution in percent.
   299  	 */
   300      public function getPercentage(Void):Number {
   301  		return percent;
   302  	}
   303  	
   304  	/**
   305  	 * Internal Update of percentage
   306  	 * 
   307  	 * @param cP Percentage of the current process.
   308  	 */
   309  	private function updatePercent(cP:Number):Void {
   310  		percent = 100/list.length*(current+(1/100*cP));
   311  	}
   312  	
   313  	
   314  	/**
   315  	 * Internal method to send error events.
   316  	 * 
   317  	 * @param error error to be provided
   318  	 * @return {@code true} to consume the event
   319  	 */
   320  	private function sendErrorEvent(error):Boolean {
   321  		var result:Boolean = false;
   322  		if (super.sendErrorEvent(error)) {
   323  			return true;
   324  		}
   325  		var errorDistributor:BatchErrorListener =
   326  				dC.getDistributor(BatchErrorListener);
   327  		return errorDistributor.onBatchError(this, error);
   328  	}
   329  	
   330  	/**
   331  	 * Internal method to send finish events.
   332  	 */
   333  	private function sendFinishEvent(Void):Void {
   334  		super.sendFinishEvent();
   335  		var finishDistributor:BatchFinishListener =
   336  				dC.getDistributor(BatchFinishListener);
   337  		finishDistributor.onBatchFinish(this);
   338  	}
   339  	
   340  	/**
   341  	 * Internal method to send pause events.
   342  	 */
   343  	private function sendPauseEvent(Void):Void {
   344  		sendUpdateEvent();
   345  	}
   346  	
   347  	/**
   348  	 * Internal method to send resume events.
   349  	 */
   350  	private function sendResumeEvent(Void):Void {
   351  		sendUpdateEvent();
   352  	}
   353  	
   354  	/**
   355  	 * Internal method to send update events.
   356  	 */
   357  	private function sendUpdateEvent(Void):Void {
   358  		super.sendUpdateEvent();
   359  		var finishDistributor:BatchUpdateListener =
   360  				dC.getDistributor(BatchUpdateListener);
   361  		finishDistributor.onBatchUpdate(this);
   362  	}
   363  	
   364  	/**
   365  	 * Internal method to send start events.
   366  	 */
   367  	private function sendStartEvent(Void):Void {
   368  		super.sendStartEvent();
   369  		var finishDistributor:BatchStartListener =
   370  				dC.getDistributor(BatchStartListener);
   371  		finishDistributor.onBatchStart(this);
   372  	}
   373  }
   374