package net.nightwhistler.pageturner.scheduling;
import android.util.Log;
import jedi.option.Option;
import java.util.LinkedList;
import static jedi.option.Options.none;
import static jedi.option.Options.some;
/**
* Generic task scheduling queue.
*
* Allows for consistent execution and cancelling of tasks
* across Android versions.
*
* @author Alex Kuiper
*/
public class TaskQueue {
public static interface TaskQueueListener {
void queueEmpty();
}
private LinkedList<QueuedTask<?,?,?>> taskQueue = new LinkedList<QueuedTask<?, ?, ?>>();
private TaskQueueListener listener;
public boolean isEmpty() {
return taskQueue.isEmpty();
}
public <A,B,C> void executeTask( QueueableAsyncTask<A,B,C> task, A... parameters ) {
task.setCallback(this::taskCompleted);
this.taskQueue.add(new QueuedTask<>(task, parameters));
Log.d("TaskQueue", "Scheduled task of type " + task
+ " total tasks scheduled now: " + this.taskQueue.size() );
if ( this.taskQueue.size() == 1 ) {
Log.d("TaskQueue", "Starting task " + taskQueue.peek() + " since task queue is 1.");
this.taskQueue.peek().execute();
}
}
/**
* Cancels the currently running task and queues this task ahead of all others.
*
* @param task
* @param parameters
* @param <A>
* @param <B>
* @param <C>
*/
public <A,B,C> void jumpQueueExecuteTask( QueueableAsyncTask<A,B,C> task, A... parameters ) {
Log.d("TaskQueue", "Queue-jump requested for " + task.getClass().getSimpleName() );
if ( this.taskQueue.isEmpty() ) {
Log.d("TaskQueue", "Delegating to simple schedule since the queue is empty.");
executeTask(task, parameters);
} else {
QueuedTask top = taskQueue.remove();
Log.d("TaskQueue", "Cancelling task of type " + top );
top.cancel();
task.setCallback(this::taskCompleted);
taskQueue.add( 0, new QueuedTask<A, B, C>(task, parameters));
Log.d("TaskQueue", "Starting task of type " + taskQueue.peek()
+ " with queue " + getQueueAsString() );
taskQueue.peek().execute();
}
}
public void clear() {
Log.d("TaskQueue", "Clearing task queue.");
if ( ! this.taskQueue.isEmpty() ) {
QueuedTask front = taskQueue.peek();
Log.d("TaskQueue", "Canceling task of type: " + front );
front.cancel();
this.taskQueue.clear();
} else {
Log.d("TaskQueue", "Nothing to do, since queue was already empty.");
}
}
public void setTaskQueueListener( TaskQueueListener listener ) {
this.listener = listener;
}
private String getQueueAsString() {
StringBuilder builder = new StringBuilder("[");
for ( int i=0; i < this.taskQueue.size(); i++ ) {
builder.append( this.taskQueue.get(i) );
if ( i < this.taskQueue.size() -1 ) {
builder.append(", ");
}
}
builder.append("]");
return builder.toString();
}
private Option<QueuedTask<?,?,?>> findQueuedTaskFor( QueueableAsyncTask<?,?,?> task ) {
for ( QueuedTask<?,?,?> wrapper: this.taskQueue ) {
if ( wrapper.getTask() == task ) {
return some(wrapper);
}
}
return none();
}
public void taskCompleted(QueueableAsyncTask<?, ?, ?> task, boolean wasCancelled) {
if ( ! wasCancelled ) {
Log.d( "TaskQueue", "Completion of task of type " + task );
QueuedTask queuedTask = this.taskQueue.remove();
if ( queuedTask.getTask() != task ) {
String errorMsg = "Tasks out of sync! Expected "+
queuedTask.getTask() + " but got " + task +
" with queue: " + getQueueAsString();
Log.e("TaskQueue", errorMsg );
throw new RuntimeException(errorMsg);
}
} else {
Log.d("TaskQueue", "Got taskCompleted for task " + task + " which was cancelled.");
findQueuedTaskFor( task ).forEach( this.taskQueue::remove );
}
Log.d("TaskQueue", "Total tasks scheduled now: " + this.taskQueue.size()
+ " with queue: " + getQueueAsString() );
if ( ! this.taskQueue.isEmpty() ) {
if ( ! this.taskQueue.peek().isExecuting() ) {
Log.d("TaskQueue", "Executing task " + this.taskQueue.peek() );
this.taskQueue.peek().execute();
} else {
Log.d("TaskQueue", "Task at the head of queue is already running.");
}
} else if ( this.listener != null ) {
Log.d("TaskQueue", "Notifying that the queue is empty.");
this.listener.queueEmpty();
}
}
}