com.ajjpj.afoundation.concurrent.AFutureHelper Maven / Gradle / Ivy
package com.ajjpj.afoundation.concurrent;
import com.ajjpj.afoundation.function.AFunction1;
import com.ajjpj.afoundation.function.AFunction2;
import com.ajjpj.afoundation.function.AStatement2NoThrow;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* This class provides a collection of static helper functions for working with {@link AFuture}s.
*
* @author arno
*/
@SuppressWarnings ("Convert2Lambda")
public class AFutureHelper {
/**
* This method combines a collection of futures into a single future.
*
* Conceptually, it holds a single value u
with an initial value of initial
. When a future finishes successfully,
* the function function
is called with the old value of u
and the future's value; the function's result is then
* used as the new value of u
. When all futures' values have thus been applied, the final value of u
is the resulting
* future's value.
*
* This is of course done in a fully non-blocking way.
*
* If one of the futures fails, the 'collected' future fails as well.
*/
public static AFuture collect (Collection> futures, final U initial, final AFunction2 function) {
if (futures.isEmpty ()) {
throw new IllegalArgumentException ("only non-empty collections can be lifted");
}
AFuture result = null;
for (final AFuture f: futures) {
if (result == null) {
result = f.mapSync (new AFunction1 () {
@Override public U apply (T param) throws E {
return function.apply (initial, param);
}
});
}
else {
result = result.mapSync (new AFunction1 () {
@Override public U apply (U param) throws Exception {
return function.apply (param, f.get ());
}
});
}
}
return result;
}
/**
* This method combines a collection of futures into a single future with the combined results of that list
* as its value. If one of the original futures fails (or times out), that causes the resulting future to
* fail with the same exception as the cause.
*
* This the generalization of {@link AFuture#zip(AFuture)} and {@link AFuture#zip(AFuture, AFuture)}.
*/
@SuppressWarnings ("unchecked")
public static AFuture> lift (Collection> futures) {
if (futures.isEmpty ()) {
throw new IllegalArgumentException ("only non-empty collections can be lifted");
}
final AFutureImpl> result = ((AFutureImpl) futures.iterator ().next ()).unscheduled ();
final ListCollector collector = new ListCollector<> (futures.size ());
int idx = 0;
for (AFuture f: futures) {
final int curIdx = idx;
f.onFinished (new AStatement2NoThrow () {
@Override public void apply (T param1, Throwable param2) {
if (param2 != null) {
result.setException (param2);
}
else {
if (collector.setValue (curIdx, param1) == 0) {
result.set ((List) Arrays.asList (collector.data));
}
}
}
});
idx += 1;
}
return result;
}
public static AFuture anyOf (AFuture... futures) {
return anyOf (Arrays.asList (futures));
}
/**
* Combines a collection of futures into a single future on a 'first come, first serve' base. The first
* of the original futures to finish successfully determines the value of the resulting future. Failures
* of some of the original futures are ignored; only if (and when) they all fail, the resulting future
* fails as well, giving the cause of failure of the last original future as its own cause of failure.
*/
@SuppressWarnings ("unchecked")
public static AFuture anyOf (Collection> futures) {
if (futures.isEmpty ()) {
throw new IllegalArgumentException ("'anyOf' only supports non-empty collections");
}
final AFutureImpl result = ((AFutureImpl) futures.iterator ().next ()).unscheduled ();
final AtomicInteger numUnfinished = new AtomicInteger (futures.size ());
final AStatement2NoThrow listener = new AStatement2NoThrow () {
@Override public void apply (T param1, Throwable param2) {
if (param2 == null) {
result.set (param1);
}
final int remaining = numUnfinished.decrementAndGet ();
if (remaining == 0) {
result.setException (param2);
}
}
};
for (AFuture f: futures) {
f.onFinished (listener);
}
return result;
}
private static class ListCollector {
volatile Object[] data;
final AtomicInteger numEmpty;
public ListCollector (int size) {
this.data = new Object[size];
numEmpty = new AtomicInteger (size);
}
@SuppressWarnings ("SillyAssignment")
int setValue (int idx, T value) {
data[idx] = value;
data = data; // to ensure visibility of the above write
return numEmpty.decrementAndGet ();
}
}
}