blob: a9f713cfb4fbcc680ffe18d7ae2b9ee4850885ce [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 IBM Corporation and others.
* 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
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.runtime;
/**
* <p>A progress monitor that uses a given amount of work ticks from a parent monitor. This is intended as a
* safer, easier-to-use alternative to SubProgressMonitor. The main benefits of SubMonitor over
* SubProgressMonitor are:</p>
* <ul>
* <li>It is not necessary to call beginTask() or done() on an instance of SubMonitor.</li>
* <li>SubMonitor has a simpler syntax for creating nested monitors.</li>
* <li>SubMonitor is more efficient for deep recursion chains.</li>
* <li>SubMonitor has a setWorkRemining method that allows the remaining space on the monitor to be
* redistributed without reporting any work.</li>
* <li>SubMonitor protects the caller from common progress reporting bugs in a called method. For example,
* if a called method fails to call done() on the given monitor or fails to consume all the ticks on
* the given monitor, the parent will correct the problem after the method returns.</li>
* </ul>
* <p></p>
* <p><b>USAGE:</b></p>
*
* <p>When implementing a method that accepts an IProgressMonitor:</p>
* <ul>
* <li>At the start of your method, use <code>SubMonitor.convert(...).</code> to convert the IProgressMonitor
* into a SubMonitor. </li>
* <li>Use <code>SubMonitor.newChild(...)</code> whenever you need to call another method that
* accepts an IProgressMonitor.</li>
* </ul>
* <p></p>
* <p><b>DEFAULT BEHAVIOR:</b></p>
*
* <p>When writing JavaDoc for a method that accepts an IProgressMonitor, you should assume the
* following default behavior unless the method's JavaDoc says otherwise:</p>
* <ul>
* <li>It WILL call beginTask on the IProgressMonitor.</li>
* <li>It WILL NOT accept a null argument.</li>
* <li>It WILL call done on the IProgressMonitor.</li>
* </ul>
* <p></p>
* <p><b>BEST PRACTISES:</b></p>
*
* <p>We recommend that newly-written methods follow the given contract:</p>
* <ul>
* <li>It WILL call beginTask on the IProgressMonitor.</li>
* <li>It WILL accept a null argument, indicating that no progress should be reported and the operation cannot be cancelled.</li>
* <li>It WILL NOT call done on the IProgressMonitor, leaving this responsibility up to the caller.</li>
* </ul>
* <p>If you wish to follow these conventions, you may copy and paste the following text into your method's JavaDoc:</p>
*
* <pre>@param monitor the progress monitor to use for reporting progress to the user. It is the caller's responsibility
* to call done() on the given monitor. Accepts <code>null</code>, indicating that no progress should be
* reported and that the operation cannot be cancelled.</pre>
*
* <p></p>
* <p><b>Example: Recommended usage</b></p>
*
* <p>This example demonstrates how the recommended usage of <code>SubMonitor</code> makes it unnecessary to call
* IProgressMonitor.done() in most situations.</p>
*
* <p>It is never necessary to call done() on a monitor obtained from <code>begin</code> or <code>progress.newChild()</code>.
* In this example, there is no guarantee that <code>monitor</code> is an instance of <code>SubMonitor</code>, making it
* necessary to call <code>monitor.done()</code>. The JavaDoc contract makes this the responsibility of the caller.</p>
*
* <pre>
* // param monitor the progress monitor to use for reporting progress to the user. It is the caller's responsibility
* // to call done() on the given monitor. Accepts <code>null</code>, indicating that no progress should be
* // reported and that the operation cannot be cancelled.
* //
* void doSomething(IProgressMonitor monitor) {
* // Convert the given monitor into a progress instance
* SubMonitor progress = SubMonitor.convert(monitor, 100);
*
* // Use 30% of the progress to do some work
* doSomeWork(progress.newChild(30));
*
* // Advance the monitor by another 30%
* progress.worked(30);
*
* // Use the remaining 40% of the progress to do some more work
* doSomeWork(progress.newChild(40));
* }
* </pre>
*
*
* <p></p>
* <p><b>Example: Default usage</b></p>
*
* <p>You will often need to implement a method that does not explicitly stipulate that calling done() is the responsibility
* of the caller. In this case, you should use the following pattern:</p>
*
* <pre>
* // param monitor the progress monitor to use for reporting progress to the user, or <code>null</code> indicating
* // that no progress should be reported and the operation cannot be cancelled.
* //
* void doSomething(IProgressMonitor monitor) {
* // Convert the given monitor into a progress instance
* SubMonitor progress = SubMonitor.convert(monitor, 100);
* try {
* // Use 30% of the progress to do some work
* doSomeWork(progress.newChild(30));
*
* // Advance the monitor by another 30%
* progress.worked(30);
*
* // Use the remaining 40% of the progress to do some more work
* doSomeWork(progress.newChild(40));
*
* } finally {
* if (monitor != null) {
* monitor.done();
* }
* }
* }
* </pre>
*
* <p></p>
* <p><b>Example: Branches</b></p>
*
* <p>This example demonstrates how to smoothly report progress in situations where some of the work is optional.</p>
*
* <pre>
* void doSomething(IProgressMonitor monitor) {
* SubMonitor progress = SubMonitor.convert(monitor, 100);
*
* if (condition) {
* // Use 50% of the progress to do some work
* doSomeWork(progress.newChild(50));
* }
*
* // Don't report any work, but ensure that we have 50 ticks remaining on the progress monitor.
* // If we already consumed 50 ticks in the above branch, this is a no-op. Otherwise, the remaining
* // space in the monitor is redistributed into 50 ticks.
*
* progress.setWorkRemaining(50);
*
* // Use the remainder of the progress monitor to do the rest of the work
* doSomeWork(progress.newChild(50));
* }
* </pre>
*
* <p>Please beware of the following anti-pattern:</p>
*
* <pre>
* if (condition) {
* // Use 50% of the progress to do some work
* doSomeWork(progress.newChild(50));
* } else {
* // Bad: Causes the progress monitor to appear to start at 50%, wasting half of the
* // space in the monitor.
* progress.worked(50);
* }
* </pre>
*
*
* <p></p>
* <p><b>Example: Loops</b></p>
*
* <p>This example demonstrates how to report progress in a loop.</p>
*
* <pre>
* void doSomething(IProgressMonitor monitor, Collection someCollection) {
* SubMonitor progress = SubMonitor.convert(monitor, 100);
*
* // Create a new progress monitor that uses 70% of the total progress and will allocate one tick
* // for each element of the given collection.
* SubMonitor loopProgress = progress.newChild(70).setWorkRemaining(someCollection.size());
*
* for (Iterator iter = someCollection.iterator(); iter.hasNext();) {
* Object next = iter.next();
*
* doWorkOnElement(next, loopProgress.newChild(1));
* }
*
* // Use the remaining 30% of the progress monitor to do some work outside the loop
* doSomeWork(progress.newChild(30));
* }
* </pre>
*
*
* <p></p>
* <p><b>Example: Infinite progress</b></p>
*
* <p>This example demonstrates how to report logarithmic progress in situations where the number of ticks
* cannot be easily computed in advance.</p>
*
* <pre>
* void doSomething(IProgressMonitor monitor, LinkedListNode node) {
* SubMonitor progress = SubMonitor.convert(monitor, 100);
*
* while (node != null) {
* // Regardless of the amount of progress reported so far,
* // use 5% of the space remaining in the monitor to process the next node.
* progress.setWorkRemaining(100);
*
* doWorkOnElement(node, progress.newChild(5));
*
* node = node.next;
* }
* }
* </pre>
*
* <p>
* This class can be used without OSGi running.
* </p>
*
* @since org.eclipse.equinox.common 3.3
*/
public final class SubMonitor implements IProgressMonitorWithBlocking {
/**
* 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 struct holds information about the root progress monitor. A SubMonitor and
* its active descendents share the same RootInfo struct.
*/
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;
/**
* 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 (eq(taskName, this.taskName)) {
return;
}
this.taskName = taskName;
root.setTaskName(taskName);
}
public void subTask(String name) {
if (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);
}
}
/**
* 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;
/**
* Creates a new SubMonitor that will report its progress via
* the given RootInfo.
*
* @param toAdapt IProgressMonitor where progress should be reported
* @param totalWork total work to perform on the given progress monitor
*/
private SubMonitor(RootInfo rootInfo, int totalWork, int availableToChildren) {
root = rootInfo;
totalParent = totalWork;
this.totalForChildren = availableToChildren;
}
/**
* <p>Converts an unknown (possibly null) IProgressMonitor into a SubMonitor allocated
* with the given number of ticks. It is not necessary to call done() on the result,
* but the caller is responsible for calling done() on the argument. Calls beginTask
* on the argument.</p>
*
* <p>This method should generally be called at the beginning of a method that accepts
* an IProgressMonitor in order to convert the IProgressMonitor into a SubMonitor.</p>
*
* @param monitor monitor to convert to a SubMonitor instance or null. Treats null
* as a new instance of <code>NullProgressMonitor</code>.
* @param work number of ticks that will be available in the resulting monitor
* @return a SubMonitor instance that adapts the argument
*/
public static SubMonitor convert(IProgressMonitor monitor, int work) {
return convert(monitor, "", work); //$NON-NLS-1$
}
/**
* <p>Converts an unknown (possibly null) IProgressMonitor into a SubMonitor allocated
* with the given number of ticks. It is not necessary to call done() on the result,
* but the caller is responsible for calling done() on the argument. Calls beginTask
* on the argument.</p>
*
* <p>This method should generally be called at the beginning of a method that accepts
* an IProgressMonitor in order to convert the IProgressMonitor into a SubMonitor.</p>
*
* @param monitor to convert into a SubMonitor instance or null. If given a null argument,
* the resulting SubMonitor will not report its progress anywhere.
* @param taskName user readable name to pass to monitor.beginTask. Never null.
* @param work initial number of ticks to allocate for children of the SubMonitor
* @return a new SubMonitor instance that is a child of the given monitor
*/
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)
return ((SubMonitor) monitor).setWorkRemaining(work);
monitor.beginTask(taskName, MINIMUM_RESOLUTION);
return new SubMonitor(new RootInfo(monitor), MINIMUM_RESOLUTION, work);
}
/**
* <p>Sets the work remaining for this SubMonitor instance. This is the total number
* of ticks that may be reported by all subsequent calls to worked(int), newChild(int), etc.
* This may be called many times for the same SubMonitor instance. When this method
* is called, the remaining space on the progress monitor is redistributed into the given
* number of ticks.</p>
*
* <p>It doesn't matter how much progress has already been reported with this SubMonitor
* instance. If you call setTicks(100), you will be able to report 100 more ticks of
* work before the progress meter reaches 100%.</p>
*
* @param workRemaining total number of remaining ticks
* @return the receiver
*/
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
* @return
*/
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()
*/
public boolean isCanceled() {
return root.isCanceled();
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitor#setTaskName(java.lang.String)
*/
public void setTaskName(String name) {
root.setTaskName(name);
}
/* (non-Javadoc)
* Starts a new main task. Since this progress monitor is a sub
* progress monitor, the given name will NOT be used to update
* the progress bar's main task label. That means the given
* string will be ignored.
* @see org.eclipse.core.runtime.IProgressMonitor#beginTask(java.lang.String, int)
*/
public void beginTask(String name, int totalWork) {
setWorkRemaining(totalWork);
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitor#done()
*/
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)
*/
public void internalWorked(double work) {
int delta = consume(work);
if (delta != 0)
root.worked(delta);
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitor#subTask(java.lang.String)
*/
public void subTask(String name) {
root.subTask(name);
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitor#worked(int)
*/
public void worked(int work) {
internalWorked(work);
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitor#setCanceled(boolean)
*/
public void setCanceled(boolean b) {
root.setCanceled(b);
}
/**
* <p>Creates a sub progress monitor that will consume the given number of ticks from the
* receiver. It is not necessary to call <code>beginTask</code> or <code>done</code> on the
* result. However, the resulting progress monitor will not report any work after the first
* call to done() or before ticks are allocated. Ticks may be allocated by calling beginTask
* or setWorkRemaining.</p>
*
* <p>Each SubMonitor only has one active child at a time. Each time newChild() is called, the
* result becomes the new active child and any unused progress from the previously-active child is
* consumed.</p>
*
* <p>This is property makes it unnecessary to call done() on a SubMonitor instance, since child
* monitors are automatically cleaned up the next time the parent is touched.</p>
*
* <code><pre>
* ////////////////////////////////////////////////////////////////////////////
* // Example 1: Typical usage of newChild
* void myMethod(IProgressMonitor parent) {
* SubMonitor progress = SubMonitor.convert(parent, 100);
* doSomething(progress.newChild(50));
* doSomethingElse(progress.newChild(50));
* }
*
* ////////////////////////////////////////////////////////////////////////////
* // Example 2: Demonstrates the function of active children. Creating children
* // is sufficient to smoothly report progress, even if worked(...) and done()
* // are never called.
* void myMethod(IProgressMonitor parent) {
* SubMonitor progress = SubMonitor.convert(parent, 100);
*
* for (int i = 0; i < 100; i++) {
* // Creating the next child monitor will clean up the previous one,
* // causing progress to be reported smoothly even if we don't do anything
* // with the monitors we create
* progress.newChild(1);
* }
* }
*
* ////////////////////////////////////////////////////////////////////////////
* // Example 3: Demonstrates a common anti-pattern
* void wrongMethod(IProgressMonitor parent) {
* SubMonitor progress = SubMonitor.convert(parent, 100);
*
* // WRONG WAY: Won't have the intended effect, as only one of these progress
* // monitors may be active at a time and the other will report no progress.
* callMethod(progress.newChild(50), computeValue(progress.newChild(50)));
* }
*
* void rightMethod(IProgressMonitor parent) {
* SubMonitor progress = SubMonitor.convert(parent, 100);
*
* // RIGHT WAY: Break up method calls so that only one SubMonitor is in use at a time.
* Object someValue = computeValue(progress.newChild(50));
* callMethod(progress.newChild(50), someValue);
* }
* </pre></code>
*
* @param totalWork number of ticks to consume from the reciever
* @return new sub progress monitor that may be used in place of a new SubMonitor
*/
public SubMonitor newChild(int totalWork) {
double totalWorkDouble = totalWork;
totalWorkDouble = Math.min(totalWorkDouble, totalForChildren - usedForChildren);
cleanupActiveChild();
SubMonitor result = new SubMonitor(root, consume(totalWorkDouble), 0);
lastSubMonitor = result;
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()
*/
public void clearBlocked() {
root.clearBlocked();
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IProgressMonitorWithBlocking#setBlocked(org.eclipse.core.runtime.IStatus)
*/
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);
}
}