Wednesday, March 21, 2007

Recently I had to learn a little bit about how to report the progress of an operation. I mean this component which is shown below and which you possibly very often see while using Eclipse:

Never before I had to take care about this, so I knew only that, there were some progress monitors, which I had to pass to some methods. I started digging into the web and found two very good articles: How to Correctly and Uniformly Use Progress Monitors and FAQ How do I use progress monitors? . Both documents contain very useful information and give nice examples. However, these two papers describe the approach in which class SubProgressMonitoris used. This solution is good, but why use good solution, when better one can be used? Since Eclipse 3.3 class SubMonitoris available (in the project org.eclipse.equinox.common in package org.eclipse.core.runtime) and this is a very good news. Why?Basically because:

we don't have to call methods beginTask() and done()on the instance of SubMonitor class

it is easier now to create submonitors (there is a method newChild(int totalWork))

in SubMonitor we can find method setWorkRemining() which allows us to redistribute the remaining space on the progress monitor

SubMonitor protects the caller from common progress reporting bugs in a called method

I was trying to find some information in the Web concerning SubMonitor, but I failed. Fortunately I found useful info in the javadoc, so I decided to make it more public to make this solution more popular.First of all we have to convert any other kind of progress monitor into SubMonitor. This is very easy, because we can use one of two static methods:publicstatic SubMonitor convert(IProgressMonitor monitor, int work)andpublicstatic SubMonitor convert(IProgressMonitor monitor, String taskName, int work)So after invoking:SubMonitor progress = SubMonitor.convert(monitor, 100);we can benefit from all the advances of SubMonitor class.The basic example of using SubMonitor could look like this:

void someOperation(IProgressMonitor monitor) {

// Convertion of the given monitorSubMonitor progressSubMonitor = SubMonitor.convert(monitor, 100);

// 70% of work is consumeddoSomeWork(progressSubMonitor.newChild(70));

// 30% of work is consumedprogressSubMonitor.worked(30);

}

My main concern about reporting the progress was how to manipulate the number of work items, when some operation will or will not be executed (so when we have a condition). Fortunately with SubMonitor it is pretty easy. The example below shows how to do this:void someOperation(IProgressMonitor monitor) {

SubMonitor progressSubMonitor = SubMonitor.convert(monitor, 100);

if(condition) { // 50% of work is consumed doSomeWork(progressSubMonitor.newChild(40)); }

//if there is any work item resumed from the first 40% then use it

progress.setWorkRemaining(40);

// the rest of work items is consumed

doSomeWork(progress.newChild(60));

}

It is also easy to handle the loop case:void someOperation(IProgressMonitor monitor, Collection someCollection) {

SubMonitor progressSubMonitor = SubMonitor.convert(monitor, 100);

// Create a new progress monitor that uses 70% of the total progressSubMonitor loopProgress = progressSubMonitor.newChild(70);

// Allocate one tick for each element of the given collection. loopProgress.setWorkRemaining(someCollection.size());

for (Iterator iter = someCollection.iterator(); iter.hasNext();) { Object next = iter.next(); //Do something with the element from collection and //consume one work item doSomeWorkOnElement(next, loopProgress.newChild(1)); }

// Use the remaining 30% of the progress monitor to do some work outside // the loopdoSomeWork(progressSubMonitor .newChild(30));