
/*
* Copyright (c) 2001-2004 Ant-Contrib project.  All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ise.antelope.tasks;


import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;


import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.TaskContainer;
import org.apache.tools.ant.types.EnumeratedAttribute;

import ise.antelope.tasks.typedefs.TimeUnit;


/**
 * Limits the amount of time that a task or set of tasks can run. This is useful
 * for tasks that may "hang" or otherwise not complete in a timely fashion. This
 * task is done when either the maxwait time has expired or all nested tasks are
 * complete, whichever is first.
 *
 * <p>Developed for use with Antelope, migrated to ant-contrib Oct 2003.
 *
 * @author    Dale Anson
 * @author    Robert D. Rice
 * @version   $Revision: 138 $
 * @since Ant 1.5
 */
public class Limit extends Task implements TaskContainer {


   // storage for nested tasks
   private Vector tasks = new Vector();


   // time units, default value is 3 minutes.
   private long maxwait = 180;
   protected TimeUnit unit = TimeUnit.SECOND_UNIT;

   // property to set if time limit is reached
   private String timeoutProperty = null;
   private String timeoutValue = "true";


   // storage for task currently executing
   private Task currentTask = null;


   // used to control thread stoppage
   private Thread taskRunner = null;


   // should the build fail if the time limit has expired? Default is no.
   private boolean failOnError = false;


   private Exception exception = null;




   /**
    * Add a task to wait on.
    *
    * @param task                A task to execute
    * @exception BuildException  won't happen
    */
   public void addTask( Task task ) throws BuildException {
      tasks.addElement( task );
   }




   /**
    * How long to wait for all nested tasks to complete, in units. 
    * Default is to wait 3 minutes.
    *
    * @param wait  time to wait, set to 0 to wait forever.
    */
   public void setMaxwait( int wait ) {
      maxwait = wait;
   }

   /**
    * Sets the unit for the max wait. Default is minutes.

    * @param unit valid values are "millisecond", "second", "minute", "hour", "day", and "week".

    */
   public void setUnit( String unit ) {
      if ( unit == null )
         return ;
      if ( unit.equals( TimeUnit.SECOND ) ) {
         setMaxWaitUnit( TimeUnit.SECOND_UNIT );
         return ;
      }
      if ( unit.equals( TimeUnit.MILLISECOND ) ) {
         setMaxWaitUnit( TimeUnit.MILLISECOND_UNIT );
         return ;
      }
      if ( unit.equals( TimeUnit.MINUTE ) ) {
         setMaxWaitUnit( TimeUnit.MINUTE_UNIT );
         return ;
      }
      if ( unit.equals( TimeUnit.HOUR ) ) {
         setMaxWaitUnit( TimeUnit.HOUR_UNIT );
         return ;
      }
      if ( unit.equals( TimeUnit.DAY ) ) {
         setMaxWaitUnit( TimeUnit.DAY_UNIT );
         return ;
      }
      if ( unit.equals( TimeUnit.WEEK ) ) {
         setMaxWaitUnit( TimeUnit.WEEK_UNIT );
         return ;
      }

   }

   /**
    * Set a millisecond wait value.
    * @param value the number of milliseconds to wait.
    */
   public void setMilliseconds( int value ) {
      setMaxwait( value );
      setMaxWaitUnit( TimeUnit.MILLISECOND_UNIT );
   }

   /**
    * Set a second wait value.
    * @param value the number of seconds to wait.
    */
   public void setSeconds( int value ) {
      setMaxwait( value );
      setMaxWaitUnit( TimeUnit.SECOND_UNIT );
   }

   /**
    * Set a minute wait value.
    * @param value the number of milliseconds to wait.
    */
   public void setMinutes( int value ) {
      setMaxwait( value );
      setMaxWaitUnit( TimeUnit.MINUTE_UNIT );
   }

   /**
    * Set an hours wait value.
    * @param value the number of hours to wait.
    */
   public void setHours( int value ) {
      setMaxwait( value );
      setMaxWaitUnit( TimeUnit.HOUR_UNIT );
   }

   /**
    * Set a day wait value.
    * @param value the number of days to wait.
    */
   public void setDays( int value ) {
      setMaxwait( value );
      setMaxWaitUnit( TimeUnit.DAY_UNIT );
   }

   /**
    * Set a week wait value.
    * @param value the number of weeks to wait.
    */
   public void setWeeks( int value ) {
      setMaxwait( value );
      setMaxWaitUnit( TimeUnit.WEEK_UNIT );
   }

   /**
    * Set the max wait time unit, default is minutes.
    */
   public void setMaxWaitUnit( TimeUnit unit ) {
      this.unit = unit;
   }


   /**
    * Determines whether the build should fail if the time limit has
    * expired on this task.
    * Default is no.
    *
    * @param fail  if true, fail the build if the time limit has been reached.
    */
   public void setFailonerror( boolean fail ) {
      failOnError = fail;
   }


   /**
    * Name the property to set after a timeout.
    *
    * @param p of property to set if the time limit has been reached.
    */
   public void setProperty( String p ) {
      timeoutProperty = p;
   }


   /**
    * The value for the property to set after a timeout, defaults to true.
    *
    * @param v for the property to set if the time limit has been reached.
    */
   public void setValue( String v ) {
      timeoutValue = v;
   }


   /**
    * Execute all nested tasks, but stopping execution of nested tasks after
    * maxwait or when all tasks are done, whichever is first.
    *
    * @exception BuildException  Description of the Exception
    */
   public void execute() throws BuildException {
      try {
         // start executing nested tasks
         final Thread runner =
            new Thread() {
               public void run() {
                  Enumeration e = tasks.elements();
                  while ( e.hasMoreElements() ) {
                     if ( taskRunner != this ) {
                        break;
                     }
                     currentTask = ( Task ) e.nextElement();
                     try {
                        currentTask.perform();
                     }
                     catch ( Exception ex ) {
                        if ( failOnError ) {
                           exception = ex;
                           return ;
                        }
                        else {
                           exception = ex;
                        }
                     }
                  }
               }
            };
         taskRunner = runner;
         runner.start();
         runner.join( unit.toMillis( maxwait ) );


         // stop executing the nested tasks
         if ( runner.isAlive() ) {
            taskRunner = null;
            runner.interrupt();
            int index = tasks.indexOf( currentTask );
            StringBuffer not_ran = new StringBuffer();
            for ( int i = index + 1; i < tasks.size(); i++ ) {
               not_ran.append( '<' ).append( ( ( Task ) tasks.get( i ) ).getTaskName() ).append( '>' );
               if ( i < tasks.size() - 1 ) {
                  not_ran.append( ", " );
               }
            }


            // maybe set timeout property
            if ( timeoutProperty != null ) {
               getProject().setNewProperty( timeoutProperty, timeoutValue );
            }


            // create output message
            StringBuffer msg = new StringBuffer();
            msg.append( "Interrupted task <" )
            .append( currentTask.getTaskName() )
            .append( ">. Waited " )
            .append( ( maxwait ) ).append( " " ).append( unit.getValue() )
            .append( ", but this task did not complete." )
            .append( ( not_ran.length() > 0 ?
                  " The following tasks did not execute: " + not_ran.toString() + "." :
                  "" ) );


            // deal with it
            if ( failOnError ) {
               throw new BuildException( msg.toString() );
            }
            else {
               log( msg.toString() );
            }
         }
         else if ( failOnError && exception != null ) {
            throw new BuildException( exception );
         }
      }
      catch ( Exception e ) {
         throw new BuildException( e );
      }
   }


}



