com.databasesandlife.util.Future Maven / Gradle / Ivy
Show all versions of java-common Show documentation
package com.databasesandlife.util;
import java.util.Iterator;
import java.util.function.Supplier;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* A future is the response of a calculation which is done in the background (in a thread).
*
* A future has the method {@link #get()} which waits for the calculation to complete, and returns the result.
* The client must create a subclass and implement the method {@link #populate()} which will be run in the thread.
*
* The reason for the creation of this class is the JVM-supplied {@link java.util.concurrent.Future} object seemed too complex.
*
* @author This source is copyright Adrian Smith and licensed under the LGPL 3.
* @see Project on GitHub
*/
public abstract class Future {
T result = null;
Exception exception = null;
Thread thread;
/** Calculate the result and return it. Must not return null. */
protected abstract @Nonnull T populate();
public static class FutureComputationTimedOutException extends Exception { }
/** An exception occurred during the population of this future */
public static class FuturePopulationException extends RuntimeException {
FuturePopulationException(@Nonnull Exception cause) { super(cause); }
}
@SuppressFBWarnings("SC_START_IN_CTOR")
public Future() {
thread = new Thread(new Runnable() {
@Override public void run() {
try {
var localResult = populate();
synchronized (Future.this) { result = localResult; }
}
catch (Exception e) {
synchronized (Future.this) { exception = e; }
}
}
}, getThreadName());
thread.start();
}
public static Future of(@CheckForNull String threadName, @Nonnull Supplier task) {
return new Future() {
protected @Nonnull Q populate() { return task.get(); }
@Override protected @Nonnull String getThreadName() { return threadName == null ? super.getThreadName() : threadName; }
};
}
public static Future of(@CheckForNull String threadName, @Nonnull Runnable task) {
return new Future() {
@Override protected @Nonnull Integer populate() { task.run(); return 0; }
@Override protected @Nonnull String getThreadName() { return threadName == null ? super.getThreadName() : threadName; }
};
}
public static Future of(@Nonnull Supplier task) {
return of(null, task);
}
public static Future of(@Nonnull Runnable task) {
return of(null, task);
}
protected String getThreadName() {
return "Future-" + getClass().getSimpleName();
}
/** Same as {@link #get()} but times out after 'seconds' seconds. */
public @Nonnull T getOrTimeoutAfterSeconds(float seconds) throws FutureComputationTimedOutException, FuturePopulationException {
try { thread.join((int) (1000000 * seconds)); }
catch (InterruptedException e) { throw new RuntimeException(e); }
synchronized (this) {
if (result == null && exception == null) throw new FutureComputationTimedOutException();
if (exception != null) throw new FuturePopulationException(exception); // wrap exception to preserve its stack backtrace
return result;
}
}
/** Returns the object, waiting for its computation to be completed if necessary. */
public @Nonnull T get() {
try { return getOrTimeoutAfterSeconds(0); }
catch (FutureComputationTimedOutException e) { throw new RuntimeException("impossible", e); }
}
/**
* An iterable whose values are computed in the background.
* The populate method must return an iterable.
*/
public abstract static class IterableFuture extends Future> implements Iterable {
@Override public Iterator iterator() { return get().iterator(); }
}
}