package me.ferry.bukkit.plugins; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.RunnableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; /** * An abstract class to perform lengthy Server-interacting tasks in a dedicated * thread. * *
* When writing a multi-threaded application using bukkit, there are two * constraints to keep in mind: *
Plugin class
* @author Ferrybig
*/
public abstract class BukkitWorker
* Note that this method is executed only once.
*
*
* Note: this method is executed in a background thread.
*
*
* @return the computed result
* @throws Exception if unable to compute a result
*
*/
protected abstract T doInBackground() throws Exception;
/**
* Executed on the Bukkit Main Server Thread after the
* {@code doInBackground} method is finished. The default implementation
* does nothing. Subclasses may override this method to perform completion
* actions on the Bukkit Main Server Thread. Note that you can query
* status inside the implementation of this method to determine the result
* of this task or whether this task has been cancelled.
*
* @see #doInBackground
* @see #isCancelled()
* @see #get
*/
protected void done() {
}
/**
* Invokes {@code done} on the Bukkit Main Server Thread.
*/
private void doneTask() {
this.doSubmit.add(new Runnable() {
@Override
public void run() {
BukkitWorker.this.done();
}
});
}
/**
* Schedules this {@code BukkitWorker} for execution on a worker
* thread.
*
*
* Note {@code BukkitWorker} is only designed to be executed once. Executing
* a {@code BukkitWorker} more than once will not result in invoking the
* {@code doInBackground} method twice.
*
*/
public final void execute() {
if (this.state == StateValue.PENDING) {
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, this.future);
}
}
/**
* {@inheritDoc}
*
* Note: calling {@code get} on the Bukkit Main Server Thread blocks
* all other tasks from being processed until this
* {@code BukkitWorker} is complete. (This is not recommend to do!)
*/
@Override
public final T get() throws InterruptedException, ExecutionException {
return this.future.get();
}
/**
* {@inheritDoc}
*
* Please refer to {@link #get} for more details.
*/
@Override
public final T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return this.future.get(timeout, unit);
}
/**
* get the plugin that created this {@code BukkitWorker}
*
* @return the plugin
*/
public final P getPlugin() {
return this.plugin;
}
/**
* Returns the {@code BukkitWorker} current state.
*
* @return the current state
*/
public final StateValue getState() {
/*
* DONE is a speacial case
* to keep getState and isDone is sync
*/
if (this.isDone()) {
return StateValue.DONE;
} else {
return this.state;
}
}
/**
* {@inheritDoc}
*/
@Override
public final boolean isCancelled() {
return this.future.isCancelled();
}
/**
* {@inheritDoc}
*/
@Override
public final boolean isDone() {
return this.future.isDone();
}
/**
* Receives data chunks from the {@code publish} method asynchronously on
* the
* Bukkit Main Server Thread.
*
*
* Please refer to the {@link #publish} method for more details.
*
* @param chunks intermediate results to process
*
* @see #publish
*
*/
protected void process(List
* Because the {@code process} method is invoked asynchronously on the
* Bukkit Main Server Thread
* multiple invocations to the {@code publish} method might occur before the
* {@code process} method is executed. For performance purposes all these
* invocations are coalesced into one invocation with concatenated
* arguments.
*
*
* For example:
*
*
* Notice, this methode is called if the plugin is stopping
*/
@Override
public final boolean cancel(boolean mayInterruptIfRunning) {
return future.cancel(mayInterruptIfRunning);
}
/**
* Computes a result, or throws an exception if unable to do so.
*
*
* publish("1");
* publish("2", "3");
* publish("4", "5", "6");
*
*
* might result in:
*
*
* process("1", "2", "3", "4", "5", "6")
*
*
*
* @param chunks intermediate results to process
*
* @see #process
*
*/
protected final void publish(V... chunks) {
synchronized (this) {
if (this.doProcess == null) {
this.doProcess = new AccumulativeRunnable