package org.gudy.azureus2.ui.swt.progress;
import java.util.*;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.ui.swt.mainwindow.MainStatusBar;
import com.aelitis.azureus.core.util.CopyOnWriteList;
/**
* A manager that aggregates and forward progress information for long running operations
* <p> This is a non-intrusive implementation, such that, it does not directly manage any of the process; it simply receives and forwards information</p>
* <p> The primary user of this class is the {@link MainStatusBar} where it is used to display progress information</p>
* @author knguyen
*
*/
public class ProgressReportingManager
implements IProgressReportConstants
{
private static ProgressReportingManager INSTANCE = null;
/**
* A custom stack to keep track of <code>ProgressReporter</code>
*/
private ProgressReporterStack progressReporters = new ProgressReporterStack();
/**
* Keeps count of all <code>ProgressReporter</code> created since this session started;
* is used as unique ID and hashCode for each instance of <code>ProgressReporter</code>
*
*/
private int reporterCounter = Integer.MIN_VALUE;
public static final int COUNT_ALL = 0;
public static final int COUNT_ACTIVE = 1;
public static final int COUNT_ERROR = 2;
/**
* A <code>CopyOnWriteList</code> of <code>IProgressReportingListener</code>
*/
private CopyOnWriteList listeners = new CopyOnWriteList();
/**
* Convenience variable tied to the parameter "auto_remove_inactive_items"
*/
private boolean isAutoRemove = false;
/**
* Private constructor
*/
private ProgressReportingManager() {
/*
* Set up isAutoRemove flag
*/
isAutoRemove = COConfigurationManager.getBooleanParameter("auto_remove_inactive_items");
COConfigurationManager.addParameterListener("auto_remove_inactive_items",
new ParameterListener() {
public void parameterChanged(String parameterName) {
isAutoRemove = COConfigurationManager.getBooleanParameter("auto_remove_inactive_items");
}
});
}
public static final synchronized ProgressReportingManager getInstance() {
if (null == INSTANCE) {
INSTANCE = new ProgressReportingManager();
}
return INSTANCE;
}
public IProgressReporter
addReporter()
{
return( new ProgressReporter( this ));
}
public IProgressReporter
addReporter(
String name )
{
return( new ProgressReporter( this, name ));
}
/**
* Returns the number of reporters that have sent any event to this manager and have not been removed
* <ul>
* <li><code>COUNT_ERROR</code> - count all reporters in error state</li>
* <li><code>COUNT_ACTIVE</code> - count all reporters that are still active</li>
* <li><code>COUNT_ALL</code> - count all reporters</li>
* </ul>
* @param whatToCount one of the above constants; will default to <code>COUNT_ALL</code> if the parameter is unrecognized
* @return
*/
public int getReporterCount(int whatToCount) {
if (whatToCount == COUNT_ERROR) {
return progressReporters.getErrorCount();
}
if (whatToCount == COUNT_ACTIVE) {
return progressReporters.getActiveCount();
}
return progressReporters.size();
}
/**
* A convenience method for quickly determining whether more than one reporter is still active.
* This method can be much quicker than calling {@link #getReporterCount()} and inspecting the returned value
* if the number of reporters is high since we may not have to go through the entire list before getting the result
*
* @return <code>true</code> if there are at least 2 active reporters; <code>false</code> otherwise
*/
public boolean hasMultipleActive() {
return progressReporters.hasMultipleActive();
}
/**
* Returns the next active reporter
* @return the next reporter that is still active; <code>null</code> if none are active or no reporters are found
*/
public IProgressReporter getNextActiveReporter() {
return progressReporters.getNextActiveReporter();
}
/**
* Returns the current reporter, in other word, the last reporter to have reported anything
* @return the last reporter; <code>null</code> if none are found
*/
public IProgressReporter getCurrentReporter() {
return progressReporters.peek();
}
/**
* Returns a modifiable list of <code>ProgressReporter</code>s; manipulating this list has no
* effect on the internal list of reporters maintained by this manager
*
* @param onlyActive <code>true</code> to filter the list to only include those reporters that are still active
* @return a sorted List of <code>ProgressReporter</code> where the oldest reporter would be at position 0
*/
public List getReporters(boolean onlyActive) {
List reporters = progressReporters.getReporters(onlyActive);
Collections.sort(reporters);
return reporters;
}
/**
*
* Returns a modifiable array of <code>ProgressReporter</code>s; manipulating this array has no
* effect on the internal list of reporters maintained by this manager
* @param onlyActive <code>true</code> to filter the array to only include those reporters that are still active
* @return a sorted array of <code>ProgressReporter</code> where the oldest reporter would be at position 0
*/
public IProgressReporter[] getReportersArray(boolean onlyActive) {
List rpList = progressReporters.getReporters(onlyActive);
IProgressReporter[] array = (IProgressReporter[]) rpList.toArray(new IProgressReporter[rpList.size()]);
Arrays.sort(array);
return array;
}
/**
* Removes the given <code>ProgressReporter</code> from this manager. This has the effect that
* any subsequent event reported by the same reporter will not be captured nor forwarded by this manager
* @param reporter
* @return
*/
public boolean remove(IProgressReporter reporter) {
boolean value = progressReporters.remove(reporter);
notifyListeners(MANAGER_EVENT_REMOVED, reporter);
return value;
}
/**
*
* @param listener
*/
public void addListener(IProgressReportingListener listener) {
if (null != listener && false == listeners.contains(listener)) {
listeners.add(listener);
}
}
/**
*
* @param listener
*/
public void removeListener(IProgressReportingListener listener) {
if (null != listener && true == listeners.contains(listener)) {
listeners.remove(listener);
}
}
/**
* Notifies listeners that the given <code>ProgressReporter</code> has been modified
* @param eventType
* @param reporter
*/
private void notifyListeners(int eventType, IProgressReporter reporter) {
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
IProgressReportingListener listener = (IProgressReportingListener) iterator.next();
if (null != listener) {
try {
listener.reporting(eventType, reporter);
} catch (Throwable e) {
Debug.out(e);
}
}
}
}
/**
* Push this reporter on top of the stack, and notifies any listeners that a state change has occurred
* @param reporter
*/
protected void notifyManager(IProgressReporter reporter) {
/*
* Update the history stack and notify listeners
*/
IProgressReport pReport = reporter.getProgressReport();
if ((true == isAutoRemove && false == pReport.isActive())
|| true == pReport.isDisposed()) {
progressReporters.remove(reporter);
notifyListeners(MANAGER_EVENT_REMOVED, reporter);
} else if (true == progressReporters.contains(reporter)) {
progressReporters.push(reporter);
notifyListeners(MANAGER_EVENT_UPDATED, reporter);
} else {
progressReporters.push(reporter);
notifyListeners(MANAGER_EVENT_ADDED, reporter);
}
}
/**
* Returns the next available ID that can be assigned to a {@link ProgressReporter}
* @return int the next available ID
*/
protected synchronized final int getNextAvailableID() {
/*
* This is a simple brute forced way to generate unique ID's which can also be use as a unique hashcode
* without having to directly track all previously created/disposed ProgressReporters
*
* This is synchronized to ensure that the incrementing of reporterCounter is consistent
* so that each ProgressReporter is guaranteed to have a unique ID (which is also used as its hashCode)
*
* WARNING: This method is mainly intended to be used by the constructors of ProgressReporter and should not be called
* from anywhere else (unless you really know what you're doing); unintended repeated call to this method can exhaust
* the limit of the integer. This counter starts from Integer.MIN_VALUE
*/
return reporterCounter++;
}
}