/************************
* This file is protected by Copyright.
* Please refer to the COPYRIGHT file distributed with this source distribution.
*
* This file is part of REDHAWK IDE.
*
* All rights reserved. This program and the accompanying materials are made available under
* the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html.
*
*/
package gov.redhawk.sca.util;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IProgressMonitorWithBlocking;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
/**
* A class with the same functionality of {@link org.eclipse.core.runtime.SubMonitor}, but with a handy function to
* give back work you realized you won't perform. See {@link #notWorked(int)}.
* @since 3.2
*/
public class SubMonitor implements IProgressMonitorWithBlocking {
/**
* Number of trivial operations (operations which do not report any progress) which can be
* performed before the monitor performs a cancellation check. This ensures that cancellation
* checks do not create a performance problem in tight loops that create a lot of SubMonitors,
* while still ensuring that cancellation is checked occasionally in such loops. This only
* affects operations which are too small to report any progress. Operations which are large
* enough to consume at least one tick will always be checked for cancellation.
*/
private static final int TRIVIAL_OPERATIONS_BEFORE_CANCELLATION_CHECK = 1000;
/**
* Minimum number of ticks to allocate when calling beginTask on an unknown IProgressMonitor.
* Pick a number that is big enough such that, no matter where progress is being displayed,
* the user would be unlikely to notice if progress were to be reported with higher accuracy.
*/
private static final int MINIMUM_RESOLUTION = 1000;
/**
* The RootInfo holds information about the root progress monitor. A SubMonitor and
* its active descendents share the same RootInfo.
*/
private static final class RootInfo {
private final IProgressMonitor root;
/**
* Remembers the last task name. Prevents us from setting the same task name multiple
* times in a row.
*/
private String taskName = null;
/**
* Remembers the last subtask name. Prevents the SubMonitor from setting the same
* subtask string more than once in a row.
*/
private String subTask = null;
/**
* Counter that indicates when we should perform an cancellation check for a trivial
* operation.
*/
int cancellationCheckCounter;
/**
* Creates a RootInfo struct that delegates to the given progress
* monitor.
*
* @param root progress monitor to delegate to
*/
public RootInfo(IProgressMonitor root) {
this.root = root;
}
public boolean isCanceled() {
return root.isCanceled();
}
public void setCanceled(boolean value) {
root.setCanceled(value);
}
public void setTaskName(String taskName) {
if (SubMonitor.eq(taskName, this.taskName)) {
return;
}
this.taskName = taskName;
root.setTaskName(taskName);
}
public void subTask(String name) {
if (SubMonitor.eq(subTask, name)) {
return;
}
this.subTask = name;
root.subTask(name);
}
public void worked(int i) {
root.worked(i);
}
public void clearBlocked() {
if (root instanceof IProgressMonitorWithBlocking) {
((IProgressMonitorWithBlocking) root).clearBlocked();
}
}
public void setBlocked(IStatus reason) {
if (root instanceof IProgressMonitorWithBlocking) {
((IProgressMonitorWithBlocking) root).setBlocked(reason);
}
}
public void checkForCancellation() {
if (root.isCanceled()) {
throw new OperationCanceledException();
}
}
}
/**
* Total number of ticks that this progress monitor is permitted to consume
* from the root.
*/
private int totalParent;
/**
* Number of ticks that this progress monitor has already reported in the root.
*/
private int usedForParent = 0;
/**
* Number of ticks that have been consumed by this instance's children.
*/
private double usedForChildren = 0.0;
/**
* Number of ticks allocated for this instance's children. This is the total number
* of ticks that may be passed into worked(int) or newChild(int).
*/
private int totalForChildren;
/**
* Children created by newChild will be completed automatically the next time
* the parent progress monitor is touched. This points to the last incomplete child
* created with newChild.
*/
private IProgressMonitor lastSubMonitor = null;
/**
* Used to communicate with the root of this progress monitor tree
*/
private final RootInfo root;
/**
* A bitwise combination of the SUPPRESS_* flags.
*/
private final int flags;
/**
* May be passed as a flag to newChild. Indicates that the calls
* to subTask on the child should be ignored. Without this flag,
* calling subTask on the child will result in a call to subTask
* on its parent.
*/
public static final int SUPPRESS_SUBTASK = 0x0001;
/**
* May be passed as a flag to newChild. Indicates that strings
* passed into beginTask should be ignored. If this flag is
* specified, then the progress monitor instance will accept null
* as the first argument to beginTask. Without this flag, any
* string passed to beginTask will result in a call to
* setTaskName on the parent.
*/
public static final int SUPPRESS_BEGINTASK = 0x0002;
/**
* May be passed as a flag to newChild. Indicates that strings
* passed into setTaskName should be ignored. If this string
* is omitted, then a call to setTaskName on the child will
* result in a call to setTaskName on the parent.
*/
public static final int SUPPRESS_SETTASKNAME = 0x0004;
/**
* May be passed as a flag to {@link #split}. Indicates that isCanceled
* should always return false.
* @since 4.1
*/
public static final int SUPPRESS_ISCANCELED = 0x0008;
/**
* May be passed as a flag to newChild. Indicates that strings
* passed to setTaskName, subTask, and beginTask should all be ignored.
*/
public static final int SUPPRESS_ALL_LABELS = SubMonitor.SUPPRESS_SETTASKNAME | SubMonitor.SUPPRESS_BEGINTASK | SubMonitor.SUPPRESS_SUBTASK;
/**
* May be passed as a flag to newChild. Indicates that strings
* passed to setTaskName, subTask, and beginTask should all be propagated
* to the parent.
*/
public static final int SUPPRESS_NONE = 0;
/**
* Creates a new SubMonitor that will report its progress via
* the given RootInfo.
* @param rootInfo the root of this progress monitor tree
* @param totalWork total work to perform on the given progress monitor
* @param availableToChildren number of ticks allocated for this instance's children
* @param flags a bitwise combination of the SUPPRESS_* constants
*/
private SubMonitor(RootInfo rootInfo, int totalWork, int availableToChildren, int flags) {
root = rootInfo;
totalParent = (totalWork > 0) ? totalWork : 0;
this.totalForChildren = availableToChildren;
this.flags = flags;
}
/**
* @see org.eclipse.core.runtime.SubMonitor#convert(IProgressMonitor)
*/
public static SubMonitor convert(IProgressMonitor monitor) {
return SubMonitor.convert(monitor, "", 0); //$NON-NLS-1$
}
/**
* @see org.eclipse.core.runtime.SubMonitor#convert(IProgressMonitor, int)
*/
public static SubMonitor convert(IProgressMonitor monitor, int work) {
return SubMonitor.convert(monitor, "", work); //$NON-NLS-1$
}
/**
* @see org.eclipse.core.runtime.SubMonitor#convert(IProgressMonitor, String, int)
*/
public static SubMonitor convert(IProgressMonitor monitor, String taskName, int work) {
if (monitor == null) {
monitor = new NullProgressMonitor();
}
// Optimization: if the given monitor already a SubMonitor, no conversion is necessary
if (monitor instanceof SubMonitor) {
monitor.beginTask(taskName, work);
return (SubMonitor) monitor;
}
monitor.beginTask(taskName, SubMonitor.MINIMUM_RESOLUTION);
return new SubMonitor(new RootInfo(monitor), SubMonitor.MINIMUM_RESOLUTION, work, SubMonitor.SUPPRESS_NONE);
}
/**
* @see org.eclipse.core.runtime.SubMonitor#setWorkRemaining(int)
*/
public SubMonitor setWorkRemaining(int workRemaining) {
// Ensure we don't try to allocate negative ticks
workRemaining = Math.max(0, workRemaining);
// Ensure we don't cause division by zero
if (totalForChildren > 0 && totalParent > usedForParent) {
// Note: We want the following value to remain invariant after this method returns
double remainForParent = totalParent * (1.0d - (usedForChildren / totalForChildren));
usedForChildren = (workRemaining * (1.0d - remainForParent / (totalParent - usedForParent)));
} else {
usedForChildren = 0.0d;
}
totalParent = totalParent - usedForParent;
usedForParent = 0;
totalForChildren = workRemaining;
return this;
}
/**
* Consumes the given number of child ticks, given as a double. Must only
* be called if the monitor is in floating-point mode.
*
* @param ticks the number of ticks to consume
* @return ticks the number of ticks to be consumed from parent
*/
private int consume(double ticks) {
if (totalParent == 0 || totalForChildren == 0) { // this monitor has no available work to report
return 0;
}
usedForChildren += ticks;
if (usedForChildren > totalForChildren) {
usedForChildren = totalForChildren;
} else if (usedForChildren < 0.0) {
usedForChildren = 0.0;
}
int parentPosition = (int) (totalParent * usedForChildren / totalForChildren);
int delta = parentPosition - usedForParent;
usedForParent = parentPosition;
return delta;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitor#isCanceled()
*/
@Override
public boolean isCanceled() {
return root.isCanceled();
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitor#setTaskName(java.lang.String)
*/
@Override
public void setTaskName(String name) {
if ((flags & SubMonitor.SUPPRESS_SETTASKNAME) == 0) {
root.setTaskName(name);
}
}
/**
* @see org.eclipse.core.runtime.SubMonitor#beginTask(String, int)
*/
@Override
public void beginTask(String name, int totalWork) {
if ((flags & SubMonitor.SUPPRESS_BEGINTASK) == 0 && name != null) {
root.setTaskName(name);
}
setWorkRemaining(totalWork);
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitor#done()
*/
@Override
public void done() {
cleanupActiveChild();
int delta = totalParent - usedForParent;
if (delta > 0) {
root.worked(delta);
}
totalParent = 0;
usedForParent = 0;
totalForChildren = 0;
usedForChildren = 0.0d;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitor#internalWorked(double)
*/
@Override
public void internalWorked(double work) {
cleanupActiveChild();
int delta = consume((work > 0.0d) ? work : 0.0d);
if (delta != 0) {
root.worked(delta);
}
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitor#subTask(java.lang.String)
*/
@Override
public void subTask(String name) {
if ((flags & SubMonitor.SUPPRESS_SUBTASK) == 0) {
root.subTask(name);
}
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitor#worked(int)
*/
@Override
public void worked(int work) {
internalWorked(work);
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitor#setCanceled(boolean)
*/
@Override
public void setCanceled(boolean b) {
root.setCanceled(b);
}
/**
* @see org.eclipse.core.runtime.SubMonitor#newChild(int)
*/
public SubMonitor newChild(int totalWork) {
return newChild(totalWork, SubMonitor.SUPPRESS_BEGINTASK);
}
/**
* @see org.eclipse.core.runtime.SubMonitor#newChild(int, int)
*/
public SubMonitor newChild(int totalWork, int suppressFlags) {
double totalWorkDouble = (totalWork > 0) ? totalWork : 0.0d;
totalWorkDouble = Math.min(totalWorkDouble, totalForChildren - usedForChildren);
cleanupActiveChild();
// Compute the flags for the child. We want the net effect to be as though the child is
// delegating to its parent, even though it is actually talking directly to the root.
// This means that we need to compute the flags such that - even if a label isn't
// suppressed by the child - if that same label would have been suppressed when the
// child delegated to its parent, the child must explicitly suppress the label.
int childFlags = SubMonitor.SUPPRESS_NONE;
if ((flags & SubMonitor.SUPPRESS_SETTASKNAME) != 0) {
// If the parent was ignoring labels passed to setTaskName, then the child will ignore
// labels passed to either beginTask or setTaskName - since both delegate to setTaskName
// on the parent
childFlags |= SubMonitor.SUPPRESS_SETTASKNAME | SubMonitor.SUPPRESS_BEGINTASK;
}
if ((flags & SubMonitor.SUPPRESS_SUBTASK) != 0) {
// If the parent was suppressing labels passed to subTask, so will the child.
childFlags |= SubMonitor.SUPPRESS_SUBTASK;
}
// Note: the SUPPRESS_BEGINTASK flag does not affect the child since there
// is no method on the child that would delegate to beginTask on the parent.
childFlags |= suppressFlags;
SubMonitor result = new SubMonitor(root, consume(totalWorkDouble), (int) totalWorkDouble, childFlags);
lastSubMonitor = result;
return result;
}
/**
* @see org.eclipse.core.runtime.SubMonitor#split(int)
* @since 4.1
*/
public SubMonitor split(int totalWork) throws OperationCanceledException {
return split(totalWork, SUPPRESS_BEGINTASK);
}
/**
* @see org.eclipse.core.runtime.SubMonitor#split(int, int)
* @since 4.1
*/
public SubMonitor split(int totalWork, int suppressFlags) throws OperationCanceledException {
int oldUsedForParent = this.usedForParent;
SubMonitor result = newChild(totalWork, suppressFlags);
if ((flags & SUPPRESS_ISCANCELED) == 0) {
int ticksTheChildWillReportToParent = result.totalParent;
// If the new child reports a nonzero amount of progress.
if (ticksTheChildWillReportToParent > 0) {
// Don't check for cancellation if the child is consuming 100% of its parent since whatever code created
// the parent already performed this check.
if (oldUsedForParent > 0 || usedForParent < totalParent) {
// Treat this as a nontrivial operation and check for cancellation unconditionally.
root.checkForCancellation();
}
} else {
// This is a trivial operation. Only perform a cancellation check after the counter expires.
if (++root.cancellationCheckCounter >= TRIVIAL_OPERATIONS_BEFORE_CANCELLATION_CHECK) {
root.cancellationCheckCounter = 0;
root.checkForCancellation();
}
}
}
return result;
}
private void cleanupActiveChild() {
if (lastSubMonitor == null) {
return;
}
IProgressMonitor child = lastSubMonitor;
lastSubMonitor = null;
child.done();
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitorWithBlocking#clearBlocked()
*/
@Override
public void clearBlocked() {
root.clearBlocked();
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitorWithBlocking#setBlocked(org.eclipse.core.runtime.IStatus)
*/
@Override
public void setBlocked(IStatus reason) {
root.setBlocked(reason);
}
protected static boolean eq(Object o1, Object o2) {
if (o1 == null) {
return (o2 == null);
}
if (o2 == null) {
return false;
}
return o1.equals(o2);
}
/**
* Reduces the work remaining for this SubMonitor. The remaining space on the progress monitor is redistributed
* into the remaining number of ticks.
*
* @param work The amount of work that will no longer be reported via {@link #worked(int)} and should be
* redistributed amongst remaining ticks.
*/
public void notWorked(int work) {
setWorkRemaining(totalForChildren - work);
}
}