/*==========================================================================*\
| $Id: InterpolatingLongResponseTask.java,v 1.1 2010/05/11 14:51:55 aallowat Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2006-2008 Virginia Tech
|
| This file is part of Web-CAT.
|
| Web-CAT is free software; you can redistribute it and/or modify
| it under the terms of the GNU Affero General Public License as published
| by the Free Software Foundation; either version 3 of the License, or
| (at your option) any later version.
|
| Web-CAT is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU Affero General Public License
| along with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package org.webcat.core;
import org.webcat.core.LongResponseTaskWithProgress;
import com.webobjects.foundation.NSTimestamp;
//-------------------------------------------------------------------------
/**
* A version of the {@link LongResponseTaskWithProgress} that supports
* discrete "steps" in the action to be performed, automatically updates
* progress bar information, and uses elapsed time for each step to
* "interpolate" progress bar information between long-running steps.
*
* @author Stephen Edwards
* @version $Id: InterpolatingLongResponseTask.java,v 1.1 2010/05/11 14:51:55 aallowat Exp $
*/
public abstract class InterpolatingLongResponseTask
extends LongResponseTaskWithProgress
{
//~ Constructors ..........................................................
// ----------------------------------------------------------
/**
* Create a new task. Subclasses that use this constructor should
* override {@link #setUpTask()} to provide the information
* necessary to interpolate progress information.
*/
public InterpolatingLongResponseTask()
{
// data members are initialized in their declarations below
}
// ----------------------------------------------------------
/**
* Create a new task. If the parameters used here are not available
* at object creation time, use the default constructor and override
* {@link #setUpTask()} instead.
* @param numberOfSteps the (unweighted) number of steps in this task
* @param stepWeights the relative weights of each step, or null (which
* causes all steps to be weighted equally)
*/
public InterpolatingLongResponseTask( int numberOfSteps, int[] stepWeights )
{
setUnweightedNumberOfSteps( numberOfSteps );
setStepWeights( stepWeights );
}
//~ Methods ...............................................................
// ----------------------------------------------------------
/**
* For progress bar displays, find out how many "units" there are
* for this entire task's total amount of work. This actually returns
* the <em>weighted</em> total, for better progress bar resolution.
* @return The total number of steps to use for this task's progress bar
*/
public synchronized int totalNumberOfSteps()
{
ensureCumulativeWeights();
return cumulativeWeights[numberOfSteps];
}
// ----------------------------------------------------------
/**
* Set the total number of (unweighted) steps for this task. This method
* is usually called by subclass constructors. Use the
* {@link #setStepWeights(int[])} method to define the relative weights
* for the steps.
* @param steps The total number of steps to use for this task's progress
* bar
*/
public synchronized void setUnweightedNumberOfSteps( int steps )
{
numberOfSteps = steps;
cumulativeWeights = null;
}
// ----------------------------------------------------------
/**
* For progress bar displays, find out how many "units" have been
* completed so far. This default implementation just returns zero
* (which is appropriate for tasks that have no progress measure).
* Tasks that override {@link #totalNumberOfSteps()} should also
* override this method.
* @return The number of steps that have been completed for this task's
* progress bar
*/
public synchronized int stepsCompletedSoFar()
{
ensureCumulativeWeights();
int result = cumulativeWeights[currentStep];
if ( currentStep < numberOfSteps )
{
int myStepWeight = weightForStep( currentStep );
if ( myStepWeight > 1 )
{
long timeNeededUntilNow = 0;
if( timeCurrent != null && timeStarted != null )
{
timeNeededUntilNow = timeCurrent.getTime() -
timeStarted.getTime();
}
long timePerWeightedUnit = 0;
if ( currentStep > 0 )
{
int denom = cumulativeWeights[currentStep];
if ( denom == 0 ) { denom = 1; }
timePerWeightedUnit = timeNeededUntilNow /
cumulativeWeights[currentStep];
}
if ( timePerWeightedUnit < 1 )
{
timePerWeightedUnit = 1000; // default is one second
}
long additionalTime = 0;
if ( timeCurrent != null )
{
additionalTime = System.currentTimeMillis() -
timeCurrent.getTime();
}
int additionalUnits =
(int)( additionalTime / timePerWeightedUnit );
if ( additionalUnits < 0 )
{
// should never happen, of course
additionalUnits = 0;
}
else if ( additionalUnits > myStepWeight )
{
additionalUnits = myStepWeight - 1;
}
result += additionalUnits;
}
}
return result;
}
// ----------------------------------------------------------
/**
* Set the relative weights for the steps in this task. By default,
* all steps are considered to have a weight of "1". Setting step weights
* will change their relative proportions when generating the corresponding
* progress bar. This method is normally called from a subclass
* constructor.
* @param weights The weights, in an array indexed from 0 to
* totalNumberOfSteps() - 1
*/
public synchronized void setStepWeights( int[] weights )
{
stepWeights = weights;
cumulativeWeights = null;
}
// ----------------------------------------------------------
/**
* Perform all the steps, updating the step counter as needed. This
* method repeatedly called {@link #nextStep(int,Object)} to perform
* each step, updating internal bookkeeping information along the way.
* @return the final result of the task
*/
public Object performAction()
{
Object result = setUpTask();
timeStarted = new NSTimestamp(); // remember when everything began...
while ( currentStep < numberOfSteps && !isCancelled() )
{
if ( log.isDebugEnabled() )
{
log.debug( "performAction(): calling nextStep("
+ currentStep + " (of " + numberOfSteps + "), "
+ result + ")");
}
result = nextStep( currentStep, result );
timeCurrent = new NSTimestamp();
currentStep++;
}
result = tearDownTask( result );
return result;
}
//~ Protected methods .....................................................
// ----------------------------------------------------------
/**
* Perform any time-consuming initialization for concrete subclasses.
* Any initialization that takes longer than a second or two should go
* here instead of in the subclass constructor. This operation is
* called as part of {@link #performAction()}, before any of the
* lower-level calls to {@link #nextStep(int,Object)} begin. It provides
* an opportunity to adjust the number of steps or the relative weights
* of steps before execution of the steps begin. This base implementation
* does nothing. Subclasses that wish to use this feature can override
* this method to do something appropriate.
* @return The return value will be used to initialize the "result" for
* the task, and will be passed into the first call to
* {@link #nextStep(int,Object)}
*/
protected Object setUpTask()
{
// Nothing to do as the default; subclasses can override as necessary
return null;
}
// ----------------------------------------------------------
/**
* Perform the next step of the task. This abstract method should
* be overridden in a concrete subclass to do "one unit" worth of
* work for the specified step.
* @param stepNumber The number of the step to execute
* @param resultSoFar The working result returned by the previous call
* to this method
* @return The new working result; the return value for the final step
* will be used as the return value for the entire action sequence
*/
protected abstract Object nextStep( int stepNumber, Object resultSoFar );
// ----------------------------------------------------------
/**
* Perform any post-processing or clean up actions for concrete subclasses.
* This operation is called as part of {@link #performAction()}, after all
* of the lower-level calls to {@link #nextStep(int,Object)} have been
* made. It provides an opportunity for finalization actions that are
* not part of <code>nextStep()</code> to be executed as part of the
* task. This base implementation does nothing. Subclasses that wish
* to use this feature can override this method to do something
* appropriate.
* @param resultSoFar The working result returned by the last call
* to nextStep()
* @return The final result
*/
protected Object tearDownTask( Object resultSoFar )
{
// Nothing to do as the default; subclasses can override as necessary
return resultSoFar;
}
// ----------------------------------------------------------
/**
* Get the relative weight for a specific step.
* @param stepNumber
* @return The weight of the given step (default is 1)
*/
public synchronized int weightForStep( int stepNumber )
{
return ( stepWeights == null || stepNumber > stepWeights.length )
? 1
: stepWeights[stepNumber];
}
// ----------------------------------------------------------
/**
* Allocate and calculate the array of cumulative step weights, if
* necessary.
*/
protected void ensureCumulativeWeights()
{
if ( cumulativeWeights == null )
{
int cumulativeWeight = 0;
cumulativeWeights = new int[numberOfSteps + 1];
for ( int i = 0; i < numberOfSteps; i++ )
{
cumulativeWeights[i] = cumulativeWeight;
cumulativeWeight += weightForStep( i );
}
cumulativeWeights[numberOfSteps] = cumulativeWeight;
}
}
//~ Instance/static variables .............................................
protected int numberOfSteps = 100;
protected int currentStep = 0;
protected int[] stepWeights = null;
protected int[] cumulativeWeights = null;
protected NSTimestamp timeStarted = null;
protected NSTimestamp timeCurrent = null;
}