![JAR search and dependency download from the Maven repository](/logo.png)
net.tascalate.concurrent.Promises Maven / Gradle / Ivy
/**
* Copyright 2015-2020 Valery Silaev (http://vsilaev.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.tascalate.concurrent;
import static net.tascalate.concurrent.SharedFunctions.cancelPromise;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Utility class to create a resolved (either successfully or faulty) {@link Promise}-s;
* to wrap an arbitrary {@link CompletionStage} interface to the {@link Promise} API;
* to combine several {@link CompletionStage}-s into aggregating promise.
*
* @author vsilaev
*
*/
public final class Promises {
private Promises() {}
/**
* Method to create a successfully resolved {@link Promise} with a value provided
* @param
* a type of the value
* @param value
* a value to wrap
* @return
* a successfully resolved {@link Promise} with a value provided
*/
public static Promise success(T value) {
return new CompletableFutureWrapper<>(
CompletableFuture.completedFuture(value)
);
}
/**
* Method to create a faulty resolved {@link Promise} with an exception provided
* @param
* a type of the value
* @param exception
* an exception to wrap
* @return
* a faulty resolved {@link Promise} with an exception provided
*/
public static Promise failure(Throwable exception) {
CompletableFuture delegate = new CompletableFuture<>();
delegate.completeExceptionally(exception);
return new CompletableFutureWrapper<>(delegate);
}
public static Promise maybe(Optional maybeValue) {
return maybeValue.map(Promises::success)
.orElseGet(() -> Promises.failure(new NoSuchElementException()));
}
/**
* Adapts a stage passed to the {@link Promise} API
* @param
* a type of the value
* @param stage
* a {@link CompletionStage} to be wrapped
* @return
* a {@link Promise}
*/
public static Promise from(CompletionStage stage) {
if (stage instanceof Promise) {
return (Promise) stage;
}
if (stage instanceof CompletableFuture) {
return new CompletableFutureWrapper<>((CompletableFuture)stage);
}
/*
return transform(stage, Function.identity(), Function.identity());
*/
return CompletionStageWrapper.from(stage);
}
public static CompletionStage withDefaultExecutor(CompletionStage stage, Executor executor) {
return new ExecutorBoundCompletionStage<>(stage, executor);
}
public static Throwable unwrapCompletionException(Throwable ex) {
return SharedFunctions.unwrapCompletionException(ex);
}
public static Promise loop(T initialValue,
Predicate super T> loopCondition,
Function super T, ? extends CompletionStage> loopBody) {
AsyncLoop asyncLoop = new AsyncLoop<>(loopCondition, loopBody);
asyncLoop.run(initialValue);
return asyncLoop;
}
public static Promise tryApply(CompletionStage resourcePromise,
Function super R, ? extends T> fn) {
return tryApply(from(resourcePromise), fn);
}
public static Promise tryApply(Promise resourcePromise,
Function super R, ? extends T> fn) {
return
resourcePromise.dependent()
.thenApply(r -> {
try (R resource = r) {
return (T)fn.apply(resource);
} catch (RuntimeException | Error rte) {
throw rte;
} catch (Throwable ex) {
throw new CompletionException(ex);
}
}, true)
.unwrap();
}
public static Promise tryApplyEx(CompletionStage resourcePromise,
Function super R, ? extends T> fn) {
return tryApplyEx(from(resourcePromise), fn);
}
public static Promise tryApplyEx(Promise resourcePromise,
Function super R, ? extends T> fn) {
return
resourcePromise.dependent()
.thenCompose(resource -> {
T result;
try {
result = fn.apply(resource);
} catch (Throwable actionException) {
try {
// Use dependent here?
return resource.close().thenCompose(__ -> failure(actionException));
} catch (Throwable onClose) {
actionException.addSuppressed(onClose);
return failure(onClose);
}
}
try {
// Use dependent here?
return resource.close().thenApply(__ -> result);
} catch (Throwable onClose) {
return failure(onClose);
}
}, true)
.unwrap();
}
public static Promise tryCompose(CompletionStage resourcePromise,
Function super R, ? extends CompletionStage> fn) {
return tryCompose(from(resourcePromise), fn);
}
public static Promise tryCompose(Promise resourcePromise,
Function super R, ? extends CompletionStage> fn) {
return
resourcePromise.dependent()
.thenCompose(resource -> {
CompletionStage action;
try {
action = fn.apply(resource);
} catch (Throwable composeException) {
try {
resource.close();
} catch (Exception onClose) {
composeException.addSuppressed(onClose);
}
return failure(composeException);
}
CompletablePromise result = new CompletablePromise<>();
action.whenComplete((actionResult, actionException) -> {
try {
resource.close();
} catch (Throwable onClose) {
if (null != actionException) {
actionException.addSuppressed(onClose);
result.onFailure(actionException);
} else {
result.onFailure(onClose);
}
// DONE WITH ERROR ON CLOSE
return;
}
// CLOSE OK
if (null == actionException) {
result.onSuccess(actionResult);
} else {
result.onFailure(actionException);
}
});
return result.onCancel(() -> cancelPromise(action, true));
}, true)
.unwrap();
}
public static Promise tryComposeEx(Promise resourcePromise,
Function super R, ? extends CompletionStage> fn) {
return
resourcePromise.dependent()
.thenCompose(resource -> {
CompletionStage action;
try {
action = fn.apply(resource);
} catch (Throwable composeException) {
try {
// Use dependent here?
return resource.close().thenCompose(__ -> failure(composeException));
} catch (Throwable onClose) {
composeException.addSuppressed(onClose);
return failure(onClose);
}
}
CompletablePromise result = new CompletablePromise<>();
action.whenComplete((actionResult, actionException) -> {
CompletionStage> afterClose;
try {
afterClose = resource.close();
} catch (Throwable onClose) {
if (null != actionException) {
actionException.addSuppressed(onClose);
result.onFailure(actionException);
} else {
result.onFailure(onClose);
}
// DONE WITH ERROR ON ASYNC CLOSE
return;
}
// ASYNC CLOSE INVOKE OK
afterClose.whenComplete((__, onClose) -> {
if (null != actionException) {
if (null != onClose) {
actionException.addSuppressed(onClose);
}
result.onFailure(actionException);
} else if (null != onClose) {
result.onFailure(onClose);
} else {
result.onSuccess(actionResult);
}
});
});
return result.onCancel(() -> cancelPromise(action, true));
}, true)
.unwrap();
}
public static Promise partitioned(Iterable extends T> values,
int batchSize,
Function super T, CompletionStage extends T>> spawner,
Collector downstream) {
return partitioned1(values.iterator(), batchSize, spawner, downstream);
}
public static Promise partitioned(Iterable extends T> values,
int batchSize,
Function super T, CompletionStage extends T>> spawner,
Collector downstream,
Executor downstreamExecutor) {
return partitioned2(values.iterator(), batchSize, spawner, downstream, downstreamExecutor);
}
public static Promise partitioned(Stream extends T> values,
int batchSize,
Function super T, CompletionStage extends T>> spawner,
Collector downstream) {
return partitioned1(values.iterator(), batchSize, spawner, downstream);
}
public static Promise partitioned(Stream extends T> values,
int batchSize,
Function super T, CompletionStage extends T>> spawner,
Collector downstream,
Executor downstreamExecutor) {
return partitioned2(values.iterator(), batchSize, spawner, downstream, downstreamExecutor);
}
private static Promise partitioned1(Iterator extends T> values,
int batchSize,
Function super T, CompletionStage extends T>> spawner,
Collector downstream) {
return
parallelStep1(values, batchSize, spawner, downstream)
.dependent()
.thenApply(downstream.finisher(), true)
.as(onCloseSource(values))
.unwrap();
}
private static Promise partitioned2(Iterator extends T> values,
int batchSize,
Function super T, CompletionStage extends T>> spawner,
Collector downstream,
Executor downstreamExecutor) {
return
parallelStep2(values, batchSize, spawner, downstream, downstreamExecutor)
.dependent()
.thenApplyAsync(downstream.finisher(), downstreamExecutor, true)
.as(onCloseSource(values))
.unwrap();
}
private static Function, Promise> onCloseSource(Object source) {
if (source instanceof AutoCloseable) {
return p -> p.dependent().whenComplete((r, e) -> {
try (AutoCloseable o = (AutoCloseable)source) {
} catch (RuntimeException | Error ex) {
if (null != e) {
e.addSuppressed(ex);
} else {
throw ex;
}
} catch (Exception ex) {
if (null != e) {
e.addSuppressed(ex);
} else {
throw new CompletionException(ex);
}
}
}, true);
} else {
return Function.identity();
}
}
private static Promise parallelStep1(Iterator extends T> values,
int batchSize,
Function super T, CompletionStage extends T>> spawner,
Collector downstream) {
int[] step = {0};
return loop(null, __ -> step[0] == 0 || values.hasNext(), current -> {
List valuesBatch = drainBatch(values, batchSize);
if (valuesBatch.isEmpty()) {
// Over
return Promises.success(step[0] == 0 ? downstream.supplier().get() : current);
} else {
List> promisesBatch =
valuesBatch.stream()
.map(spawner)
.collect(Collectors.toList());
boolean initial = step[0] == 0;
step[0]++;
return
Promises.all(promisesBatch)
.dependent()
.thenApply(vals -> accumulate(vals, initial, current, downstream), true);
}
});
}
private static Promise parallelStep2(Iterator extends T> values,
int batchSize,
Function super T, CompletionStage extends T>> spawner,
Collector downstream,
Executor downstreamExecutor) {
int[] step = {0};
return loop(null, __ -> step[0] == 0 || values.hasNext(), current -> {
List valuesBatch = drainBatch(values, batchSize);
if (valuesBatch.isEmpty()) {
// Over
Promise result;
if (step[0] == 0) {
result = CompletableTask.supplyAsync(downstream.supplier(), downstreamExecutor);
} else {
result = Promises.success(current);
}
return result;
} else {
List> promisesBatch =
valuesBatch.stream()
.map(spawner)
.collect(Collectors.toList());
boolean initial = step[0] == 0;
step[0]++;
return
Promises.all(promisesBatch)
.dependent()
.thenApplyAsync(vals -> accumulate(vals, initial, current, downstream), downstreamExecutor, true);
}
});
}
private static List drainBatch(Iterator extends T> values, int batchSize) {
List valuesBatch = new ArrayList<>(batchSize);
for (int count = 0; values.hasNext() && count < batchSize; count++) {
valuesBatch.add(values.next());
}
return valuesBatch;
}
private static A accumulate(List vals, boolean initial, A current, Collector downstream) {
A insertion = downstream.supplier().get();
vals.stream()
.forEach(v -> downstream.accumulator().accept(insertion, v));
return initial ? insertion : downstream.combiner().apply(current, insertion);
}
/**
* Returns a promise that is resolved successfully when all {@link CompletionStage}-s passed as parameters
* are completed normally; if any promise completed exceptionally, then resulting promise is resolved faulty
* as well.
*
The resolved result of this promise contains a list of the resolved results of the
* {@link CompletionStage}-s passed as an argument at corresponding positions.
*
When resulting promise is resolved faulty, all remaining incomplete {@link CompletionStage}-s are
* cancelled.
* @param
* a common supertype of the resulting values
* @param promises
* an array of {@link CompletionStage}-s to combine
* @return
* a combined promise
*/
@SafeVarargs
public static Promise> all(CompletionStage extends T>... promises) {
return all(Arrays.asList(promises));
}
public static Promise> all(List extends CompletionStage extends T>> promises) {
return all(true, promises);
}
/**
* Returns a promise that is resolved successfully when all {@link CompletionStage}-s passed as parameters
* are completed normally; if any promise completed exceptionally, then resulting promise is resolved faulty
* as well.
*
The resolved result of this promise contains a list of the resolved results of the
* {@link CompletionStage}-s passed as an argument at corresponding positions.
*
When resulting promise is resolved faulty and cancelRemaining
parameter is
* true
, all remaining incomplete {@link CompletionStage}-s are cancelled.
* @param
* a common supertype of the resulting values
* @param cancelRemaining
* when true and resulting promise is resolved faulty all incomplete {@link CompletionStage}-s are cancelled
* @param promises
* an array of {@link CompletionStage}-s to combine
* @return
* a combined promise
*/
@SafeVarargs
public static Promise> all(boolean cancelRemaining, CompletionStage extends T>... promises) {
return all(cancelRemaining, Arrays.asList(promises));
}
public static Promise> all(boolean cancelRemaining, List extends CompletionStage extends T>> promises) {
return atLeast(null != promises ? promises.size() : 0, 0, cancelRemaining, promises);
}
public static Promise