/*
* 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 net.sf.antcontrib.process;
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;
/**
* 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: 1.6 $
* @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 );
}
}
/**
* The enumeration of units:
* millisecond, second, minute, hour, day, week
* Todo: we use timestamps in many places, why not factor this out
*/
public static class TimeUnit extends EnumeratedAttribute {
public static final String MILLISECOND = "millisecond";
public static final String SECOND = "second";
public static final String MINUTE = "minute";
public static final String HOUR = "hour";
public static final String DAY = "day";
public static final String WEEK = "week";
/** static unit objects, for use as sensible defaults */
public static final TimeUnit MILLISECOND_UNIT =
new TimeUnit( MILLISECOND );
public static final TimeUnit SECOND_UNIT =
new TimeUnit( SECOND );
public static final TimeUnit MINUTE_UNIT =
new TimeUnit( MINUTE );
public static final TimeUnit HOUR_UNIT =
new TimeUnit( HOUR );
public static final TimeUnit DAY_UNIT =
new TimeUnit( DAY );
public static final TimeUnit WEEK_UNIT =
new TimeUnit( WEEK );
private static final String[] units = {
MILLISECOND, SECOND, MINUTE, HOUR, DAY, WEEK
};
private Hashtable timeTable = new Hashtable();
public TimeUnit() {
timeTable.put( MILLISECOND, new Long( 1L ) );
timeTable.put( SECOND, new Long( 1000L ) );
timeTable.put( MINUTE, new Long( 1000L * 60L ) );
timeTable.put( HOUR, new Long( 1000L * 60L * 60L ) );
timeTable.put( DAY, new Long( 1000L * 60L * 60L * 24L ) );
timeTable.put( WEEK, new Long( 1000L * 60L * 60L * 24L * 7L ) );
}
/**
* private constructor
* used for static construction of TimeUnit objects.
* @param value String representing the value.
*/
private TimeUnit( String value ) {
this( );
setValueProgrammatically( value );
}
/**
* set the inner value programmatically.
* @param value to set
*/
protected void setValueProgrammatically( String value ) {
this.value = value;
}
public long getMultiplier() {
String key = getValue().toLowerCase();
Long l = ( Long ) timeTable.get( key );
return l.longValue();
}
public String[] getValues() {
return units;
}
/**
* convert the time in the current unit, to millis
* @param numberOfUnits long expressed in the current objects units
* @return long representing the value in millis
*/
public long toMillis( long numberOfUnits ) {
return numberOfUnits * getMultiplier( );
}
}
}