package delight.async;
import delight.async.callbacks.ListCallback;
import delight.async.callbacks.SimpleCallback;
import delight.async.callbacks.ValueCallback;
import delight.async.flow.CallbackAggregator;
import delight.async.flow.CallbackMap;
import delight.async.helper.Aggregator;
import delight.functional.Closure;
import delight.functional.Closure2;
import delight.functional.Success;
import delight.functional.SuccessFail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
*
* Asynchronous operations which can be applied in a Java and JavaScript
* environment.
*
* @author Max Rohde
*
*/
public class AsyncCommon {
/**
*
* Tries to resolve a {@link Operation} immediately without waiting for the
* asynchronous operation.
*
* This is useful for operations which actually resolve in a synchronous
* fashion (which might be added for legacy logic).
*
* @param deferred
* @return
*/
public static final ResultType getDirty(final Operation deferred) {
final Value resolved = new Value(false);
final Value value = new Value(null);
final Value exception = new Value(null);
deferred.apply(new ValueCallback() {
@Override
public void onFailure(final Throwable t) {
exception.set(t);
}
@Override
public void onSuccess(final ResultType result) {
value.set(result);
resolved.set(true);
}
});
if (exception.get() != null) {
throw new RuntimeException(exception.get());
}
if (!resolved.get()) {
throw new RuntimeException("Asynchronous get could not be resolved for " + deferred);
}
return value.get();
}
/**
*
* Will apply the asynchronous operation operation to all inputs and
* call the callback once all operations are completed.
*
* Callback is also called upon the first operation which fails.
*
* ValueCallback must be called in closure.
*
* @param inputs
* @param operation
* @param callback
*/
public static void map(final List inputs,
final AsyncFunction operation, final ListCallback callback) {
final CallbackMap callbackMap = new CallbackMap(inputs, callback);
for (final InputType input : inputs) {
operation.apply(input, callbackMap.createCallback(input));
}
}
public final static SimpleCallback asSimpleCallbackAndReturnSuccess(final ValueCallback callback) {
return new SimpleCallback() {
@Override
public void onFailure(final Throwable t) {
callback.onFailure(t);
}
@Override
public void onSuccess() {
callback.onSuccess(Success.INSTANCE);
}
};
}
public final static SimpleCallback asSimpleCallback(final ValueCallback callback) {
return new SimpleCallback() {
@Override
public void onFailure(final Throwable t) {
callback.onFailure(t);
}
@Override
public void onSuccess() {
callback.onSuccess((T) Success.INSTANCE);
}
};
}
public static final ValueCallback> asListCallback(final ValueCallback callback) {
return new ValueCallback>() {
@Override
public void onFailure(final Throwable t) {
callback.onFailure(t);
}
@Override
public void onSuccess(final List value) {
callback.onSuccess(Success.INSTANCE);
}
};
}
public static final ValueCallback asValueCallback(final SimpleCallback callback) {
return new ValueCallback() {
@Override
public void onFailure(final Throwable t) {
callback.onFailure(t);
}
@Override
public void onSuccess(final T value) {
callback.onSuccess();
}
};
}
public static final Closure wrapAsClosure(final ValueCallback callback) {
return new Closure() {
@Override
public void apply(final SuccessFail o) {
if (o.isFail()) {
callback.onFailure(o.getException());
return;
}
callback.onSuccess(Success.INSTANCE);
}
};
}
public final static SimpleCallback doNothing() {
return new SimpleCallback() {
@Override
public void onFailure(final Throwable t) {
throw new RuntimeException(t);
}
@Override
public void onSuccess() {
}
};
}
public final static SimpleCallback onSuccess(final Closure closure) {
return new SimpleCallback() {
@Override
public void onFailure(final Throwable t) {
throw new RuntimeException(t);
}
@Override
public void onSuccess() {
closure.apply(Success.INSTANCE);
}
};
}
/**
*
* Creates a factory for callbacks. When these callbacks are called, their
* results are aggregated in the order in which the callbacks have been
* created .
*
* If the factory has not been called the same number of times as results
* are expected, the defined callback will be called when the created
* callbacks have been called the number of expected times.
*
* @param results
* @param callWhenCollected
* @return
*/
public final static Aggregator collect(final int results, final ValueCallback> callWhenCollected) {
return new CallbackAggregator(results, callWhenCollected);
}
@SuppressWarnings("rawtypes")
public static void parallelAr(final OP[] operations,
final ValueCallback> callback) {
parallel(Arrays.asList(operations), callback);
}
public static > void parallel(final List operations,
final ValueCallback> callback) {
parallel(operations, Integer.MAX_VALUE, callback);
}
public final static > void parallel(final List operations, final int maxParallelOps,
final ValueCallback> callback) {
if (operations.size() == 0) {
callback.onSuccess(new ArrayList(0));
return;
}
if (operations.size() <= maxParallelOps) {
final Aggregator aggregator = collect(operations.size(), callback);
for (final Operation op : operations) {
op.apply(aggregator.createCallback());
}
return;
}
final List toRun = operations.subList(0, maxParallelOps);
final List remaining = operations.subList(maxParallelOps, operations.size());
assert operations.size() == toRun.size() + remaining.size() : "Invalid list split: " + operations.size()
+ " into (" + toRun.size() + " and " + remaining.size() + ")";
parallel(toRun, maxParallelOps, AsyncCommon.embed(callback, new Closure>() {
@Override
public void apply(final List head) {
parallel(remaining, maxParallelOps, embed(callback, new Closure>() {
@Override
public void apply(final List tail) {
final List all = new ArrayList(head.size() + tail.size());
all.addAll(head);
all.addAll(tail);
callback.onSuccess(all);
}
}));
}
}));
}
public static void parallelUnsafe(final List operations, final ValueCallback> callback) {
parallel((List>) operations, callback);
}
/**
* Perform the listed operations sequentially.
*
* @param operations
* @param callback
*/
public static void sequential(final List> operations, final ValueCallback> callback) {
sequentialInt(operations, 0, new ArrayList(operations.size()), callback);
}
private static void sequentialInt(final List> operations, final int idx, final List results,
final ValueCallback> callback) {
if (idx >= operations.size()) {
callback.onSuccess(results);
return;
}
operations.get(idx).apply(new ValueCallback() {
@Override
public void onFailure(final Throwable t) {
callback.onFailure(t);
}
@Override
public void onSuccess(final R value) {
results.add(value);
sequentialInt(operations, idx + 1, results, callback);
}
});
}
/**
*
* Performs the provided operation on all elements of the provided list and
* calls a callback with all results when completed.
*
* Does not wait for the asynchronous result of one operation to proceed
* with the other. Thus if operations are implemented asynchronously, the
* operations will run in parallel.
*
* @param elements
* @param operation
* @param callback
*/
public final static void forEach(final List elements, final Closure2> operation,
final ValueCallback> callback) {
final Aggregator agg = collect(elements.size(), callback);
for (final E element : elements) {
final ValueCallback itmcb = agg.createCallback();
operation.apply(element, itmcb);
}
}
/**
*
* Embeds the closure within the provided callback.
*
* Useful for cascading callbacks while avoiding to define onFailure method
* declarations.
*
* @param toCallback
* @param onSuccess
* @return
*/
public final static ValueCallback embed(final ValueCallback> toCallback, final Closure onSuccess) {
return new ValueCallback() {
@Override
public void onFailure(final Throwable t) {
toCallback.onFailure(t);
}
@Override
public void onSuccess(final V value) {
onSuccess.apply(value);
}
};
}
public static final SimpleCallback embed(final SimpleCallback toCallback, final Runnable onSuccess) {
return new SimpleCallback() {
@Override
public void onFailure(final Throwable t) {
toCallback.onFailure(t);
}
@Override
public void onSuccess() {
onSuccess.run();
}
};
}
public static ValueCallback> asValueCallback(final ListCallback callback) {
return new ValueCallback>() {
@Override
public void onFailure(final Throwable t) {
callback.onFailure(t);
}
@Override
public void onSuccess(final List value) {
callback.onSuccess(value);
}
};
}
public static ValueCallback asValueCallback(final ValueCallback callback) {
return AsyncCommon.embed(callback, new Closure() {
@Override
public void apply(final V o) {
callback.onSuccess(Success.INSTANCE);
}
});
}
}