package com.sk89q.task;
import java.util.ArrayList;
import java.util.List;
import javax.swing.SwingWorker;
/**
* A task that can be performed in another thread, similar to
* {@link SwingWorker}.
*/
public abstract class Task implements Runnable {
private final List<ProgressListener> listeners = new ArrayList<ProgressListener>();
private Thread thread;
/**
* Add a progress listener.
*
* @param listener
* the listener
*/
public final void addProgressListener(ProgressListener listener) {
listeners.add(listener);
}
/**
* Remove a progress listener.
*
* @param listener
* the listener
*/
public final void removeProgressListener(ProgressListener listener) {
listeners.remove(listener);
}
/**
* Called when the progress changes.
*
* @param progress
* a value between 0 or 1, or -1 for indeterminate
*/
protected final void fireProgressChange(final double progress) {
for (ProgressListener listener : listeners) {
listener.progressChange(progress);
}
}
/**
* Called when the status changes.
*
* @param message
* the new status message
*/
protected final void fireStatusChange(final String message) {
for (ProgressListener listener : listeners) {
listener.statusChange(message);
}
}
/**
* Called when the task completes.
*/
private void fireComplete() {
for (ProgressListener listener : listeners) {
listener.complete();
}
}
/**
* Called when the task ends in an error.
*
* @param exception
* the exception
*/
private void fireError(final Throwable exception) {
for (ProgressListener listener : listeners) {
listener.error(exception);
}
}
/**
* Called when the task has been aborted.
*/
private void fireAborted() {
for (ProgressListener listener : listeners) {
listener.aborted();
}
}
/**
* Attach a listener to a given task in order to redirect events to this
* task.
*
* @param task
* the sub-task
* @param lower
* the lower bound for the progress events
* @param upper
* the upper bound for the progress events
*/
protected <T extends Task> T attach(T task, final double lower, final double upper) {
ProgressListener listener = new ProgressListener() {
@Override
public void statusChange(String message) {
fireStatusChange(message);
}
@Override
public void progressChange(double progress) {
if (progress < 0) {
fireProgressChange(-1);
} else {
fireProgressChange((upper - lower) * progress + lower);
}
}
@Override
public void error(Throwable exception) {
}
@Override
public void complete() {
}
@Override
public void aborted() {
}
};
task.addProgressListener(listener);
return task;
}
/**
* Run a task in another thread.
*/
public final void start() {
Thread thread = new Thread(this, getClass().getCanonicalName());
this.thread = thread;
thread.start();
}
/**
* Attempts to cancel the task by throwing an {@link InterruptedException}
* in the thread.
*
* <p>
* This should not be called if this task is not running in a different
* thread. In addition, this method is only usable if the task was started
* with {@link #start()} and not with an external executor.
* </p>
*
* <p>
* It is still possible for the task to complete or error after the task has
* been cancelled.
* </p>
*/
public void cancel() {
if (thread != null) {
thread.interrupt();
}
}
@Override
public final void run() {
try {
execute();
fireComplete();
} catch (InterruptedException t) {
handleCancel();
fireAborted();
} catch (Throwable t) {
fireError(t);
}
}
/**
* Called to perform the task.
*/
protected abstract void execute() throws Exception;
/**
* Called if the task has been cancelled.
*/
protected void handleCancel() {
}
}