package org.robolectric.android.util.concurrent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.util.Scheduler;
/**
* Executor service that runs all operations on the background scheduler.
*/
public class RoboExecutorService implements ExecutorService {
private final Scheduler scheduler;
private boolean isShutdown;
private final HashSet<Runnable> runnables = new HashSet<>();
static class AdvancingFutureTask<V> extends FutureTask<V> {
private final Scheduler scheduler;
public AdvancingFutureTask(Scheduler scheduler, Callable<V> callable) {
super(callable);
this.scheduler = scheduler;
}
public AdvancingFutureTask(Scheduler scheduler, Runnable runnable, V result) {
super(runnable, result);
this.scheduler = scheduler;
}
@Override
public V get() throws InterruptedException, ExecutionException {
while (!isDone()) {
scheduler.advanceToNextPostedRunnable();
}
return super.get();
}
}
public RoboExecutorService() {
this.scheduler = ShadowApplication.getInstance().getBackgroundThreadScheduler();
}
@Override
public void shutdown() {
shutdownNow();
}
@Override
public List<Runnable> shutdownNow() {
isShutdown = true;
List<Runnable> notExecutedRunnables = new ArrayList<>();
for (Runnable runnable : runnables) {
scheduler.remove(runnable);
notExecutedRunnables.add(runnable);
}
runnables.clear();
return notExecutedRunnables;
}
@Override
public boolean isShutdown() {
return isShutdown;
}
@Override
public boolean isTerminated() {
return isShutdown;
}
@Override
public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
// If not shut down first, timeout would occur with normal behavior.
return isShutdown();
}
@Override
public <T> Future<T> submit(Callable<T> tCallable) {
return schedule(new AdvancingFutureTask<T>(scheduler, tCallable));
}
@Override
public <T> Future<T> submit(Runnable runnable, T t) {
return schedule(new AdvancingFutureTask<T>(scheduler, runnable, t));
}
@Override
public Future<?> submit(Runnable runnable) {
return submit(runnable, null);
}
private <T> Future<T> schedule(final FutureTask<T> futureTask) {
Runnable runnable = new Runnable() {
@Override
public void run() {
futureTask.run();
runnables.remove(this);
}
};
runnables.add(runnable);
scheduler.post(runnable);
return futureTask;
}
@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> callables) throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> callables, long l, TimeUnit timeUnit) throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> callables) throws InterruptedException, ExecutionException {
throw new UnsupportedOperationException();
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> callables, long l, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException {
throw new UnsupportedOperationException();
}
@Override
public void execute(Runnable runnable) {
@SuppressWarnings({"unused", "nullness"})
Future<?> possiblyIgnoredError = submit(runnable);
}
}