io.activej.promise.AbstractPromise Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of activej-promise Show documentation
Show all versions of activej-promise Show documentation
A convenient way to organize asynchronous code.
Promises are a faster and more efficient version of JavaScript's Promise and Java's CompletionStage's.
/*
* Copyright (C) 2020 ActiveJ LLC.
*
* 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 io.activej.promise;
import io.activej.async.callback.Callback;
import io.activej.common.ApplicationSettings;
import io.activej.common.Checks;
import io.activej.common.collection.Try;
import io.activej.common.exception.UncheckedException;
import io.activej.common.recycle.Recyclers;
import org.jetbrains.annotations.Async;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import static io.activej.common.Checks.checkState;
import static io.activej.eventloop.Eventloop.getCurrentEventloop;
import static io.activej.eventloop.util.RunnableWithContext.wrapContext;
@SuppressWarnings({"unchecked", "WeakerAccess", "unused"})
abstract class AbstractPromise implements Promise {
static {
Recyclers.register(AbstractPromise.class, promise -> promise.whenResult(Recyclers::recycle));
}
private static final boolean CHECK = Checks.isEnabled(AbstractPromise.class);
private static final Object PROMISE_NOT_SET = new Object();
private static final boolean RESET_CALLBACKS = ApplicationSettings.getBoolean(AbstractPromise.class, "resetCallbacks", false);
protected T result = (T) PROMISE_NOT_SET;
@Nullable
protected Throwable exception;
@Nullable
protected Callback super T> next;
public void reset() {
this.result = (T) PROMISE_NOT_SET;
this.exception = null;
this.next = null;
}
public void resetCallbacks() {
this.next = null;
}
@Override
public final boolean isComplete() {
return result != PROMISE_NOT_SET;
}
@Override
public final boolean isResult() {
return result != PROMISE_NOT_SET && exception == null;
}
@Override
public final boolean isException() {
return exception != null;
}
@Override
public T getResult() {
return result != PROMISE_NOT_SET ? result : null;
}
@Override
public Throwable getException() {
return exception;
}
@Override
public Try getTry() {
if (isResult()) return Try.of(result);
if (isException()) return Try.ofException(exception);
return null;
}
protected void complete(@Nullable T value, @Nullable Throwable e) {
if (CHECK) checkState(!isComplete(), "Promise has already been completed");
if (e == null) {
complete(value);
} else {
completeExceptionally(e);
}
}
@Async.Execute
protected void complete(@Nullable T value) {
if (CHECK) checkState(!isComplete(), "Promise has already been completed");
result = value;
if (next != null) {
next.accept(value, null);
if (RESET_CALLBACKS) {
next = null;
}
}
}
@Async.Execute
protected void completeExceptionally(@Nullable Throwable e) {
if (CHECK) checkState(!isComplete(), "Promise has already been completed");
result = null;
exception = e;
if (next != null) {
next.accept(null, e);
if (RESET_CALLBACKS) {
next = null;
}
}
}
protected boolean tryComplete(@Nullable T value, @Nullable Throwable e) {
if (!isComplete()) {
complete(value, e);
return true;
}
return false;
}
protected boolean tryComplete(@Nullable T value) {
if (!isComplete()) {
complete(value);
return true;
}
return false;
}
protected boolean tryCompleteExceptionally(@NotNull Throwable e) {
if (!isComplete()) {
completeExceptionally(e);
return true;
}
return false;
}
@NotNull
@Override
public & Promise> Promise next(@Async.Schedule @NotNull P promise) {
if (isComplete()) {
promise.accept(result, exception);
return promise;
}
subscribe(promise);
return promise;
}
@Async.Schedule
protected void subscribe(@NotNull Callback super T> callback) {
if (CHECK) checkState(!isComplete(), "Promise has already been completed");
if (next == null) {
next = callback;
} else if (next instanceof CallbackList) {
((CallbackList) next).add(callback);
} else {
next = new CallbackList<>(next, callback);
}
}
@NotNull
@Override
public Promise map(@NotNull Function super T, ? extends U> fn) {
if (isComplete()) {
try {
return isResult() ? Promise.of(fn.apply(result)) : (Promise) this;
} catch (UncheckedException u) {
return Promise.ofException(u.getCause());
}
}
NextPromise resultPromise = new NextPromise() {
@Override
public void accept(T result, @Nullable Throwable e) {
if (e == null) {
U newResult;
try {
newResult = fn.apply(result);
} catch (UncheckedException u) {
completeExceptionally(u.getCause());
return;
}
complete(newResult);
} else {
completeExceptionally(e);
}
}
@Override
public String describe() {
return ".map(" + formatToString(fn) + ')';
}
};
subscribe(resultPromise);
return resultPromise;
}
@NotNull
@Override
public Promise mapEx(@NotNull BiFunction super T, Throwable, ? extends U> fn) {
if (isComplete()) {
try {
return Promise.of(fn.apply(result, exception));
} catch (UncheckedException u) {
return Promise.ofException(u.getCause());
}
}
NextPromise resultPromise = new NextPromise() {
@Override
public void accept(T result, Throwable e) {
if (e == null) {
U newResult;
try {
newResult = fn.apply(result, null);
} catch (UncheckedException u) {
completeExceptionally(u.getCause());
return;
}
complete(newResult);
} else {
U newResult;
try {
newResult = fn.apply(null, e);
} catch (UncheckedException u) {
completeExceptionally(u.getCause());
return;
}
complete(newResult);
}
}
@Override
public String describe() {
return ".mapEx(" + formatToString(fn) + ')';
}
};
subscribe(resultPromise);
return resultPromise;
}
@NotNull
@Override
public Promise then(@NotNull Function super T, ? extends Promise extends U>> fn) {
if (isComplete()) {
try {
return isResult() ? (Promise) fn.apply(result) : (Promise) this;
} catch (UncheckedException u) {
return Promise.ofException(u.getCause());
}
}
NextPromise resultPromise = new NextPromise() {
@Override
public void accept(T result, @Nullable Throwable e) {
if (e == null) {
Promise extends U> promise;
try {
promise = fn.apply(result);
} catch (UncheckedException u) {
completeExceptionally(u.getCause());
return;
}
promise.whenComplete(this::complete);
} else {
completeExceptionally(e);
}
}
@Override
public String describe() {
return ".then(" + formatToString(fn) + ')';
}
};
subscribe(resultPromise);
return resultPromise;
}
@Override
public @NotNull Promise then(@NotNull Supplier extends Promise extends U>> fn) {
if (isComplete()) {
try {
return isResult() ? (Promise) fn.get() : (Promise) this;
} catch (UncheckedException u) {
return Promise.ofException(u.getCause());
}
}
NextPromise resultPromise = new NextPromise() {
@Override
public void accept(T result, @Nullable Throwable e) {
if (e == null) {
Promise extends U> promise;
try {
promise = fn.get();
} catch (UncheckedException u) {
completeExceptionally(u.getCause());
return;
}
promise.whenComplete(this::complete);
} else {
completeExceptionally(e);
}
}
@Override
public String describe() {
return ".then(" + formatToString(fn) + ')';
}
};
subscribe(resultPromise);
return resultPromise;
}
@NotNull
@Override
public Promise thenEx(@NotNull BiFunction super T, Throwable, ? extends Promise extends U>> fn) {
if (isComplete()) {
try {
return (Promise) fn.apply(result, exception);
} catch (UncheckedException u) {
return Promise.ofException(u.getCause());
}
}
NextPromise resultPromise = new NextPromise() {
@Override
public void accept(T result, @Nullable Throwable e) {
if (e == null) {
Promise extends U> promise;
try {
promise = fn.apply(result, null);
} catch (UncheckedException u) {
completeExceptionally(u.getCause());
return;
}
promise.whenComplete(this::complete);
} else {
Promise extends U> promise;
try {
promise = fn.apply(null, e);
} catch (UncheckedException u) {
completeExceptionally(u.getCause());
return;
}
promise.whenComplete(this::complete);
}
}
@Override
public String describe() {
return ".thenEx(" + formatToString(fn) + ')';
}
};
subscribe(resultPromise);
return resultPromise;
}
@NotNull
@Override
public Promise whenComplete(@NotNull Callback super T> action) {
if (isComplete()) {
action.accept(result, exception);
return this;
}
subscribe(action);
return this;
}
@NotNull
@Override
public Promise whenComplete(@NotNull Runnable action) {
if (isComplete()) {
action.run();
return this;
}
subscribe(new SimpleCallback() {
@Override
public void accept(T result, @Nullable Throwable e) {
action.run();
}
@Override
public String toString() {
return ".whenComplete(" + formatToString(action) + ')';
}
});
return this;
}
@NotNull
@Override
public Promise whenResult(Consumer super T> action) {
if (isComplete()) {
if (isResult()) action.accept(result);
return this;
}
subscribe(new SimpleCallback() {
@Override
public void accept(T result, @Nullable Throwable e) {
if (e == null) {
action.accept(result);
}
}
@Override
public String toString() {
return ".whenResult(" + formatToString(action) + ')';
}
});
return this;
}
@Override
public Promise whenResult(@NotNull Runnable action) {
if (isComplete()) {
if (isResult()) action.run();
return this;
}
subscribe(new SimpleCallback() {
@Override
public void accept(T result, @Nullable Throwable e) {
if (e == null) {
action.run();
}
}
@Override
public String toString() {
return ".whenResult(" + formatToString(action) + ')';
}
});
return this;
}
@Override
public Promise whenException(@NotNull Consumer action) {
if (isComplete()) {
if (isException()) {
action.accept(exception);
}
return this;
}
subscribe(new SimpleCallback() {
@Override
public void accept(T result, @Nullable Throwable e) {
if (e != null) {
action.accept(e);
}
}
@Override
public String toString() {
return ".whenException(" + formatToString(action) + ')';
}
});
return this;
}
@Override
public Promise whenException(@NotNull Runnable action) {
if (isComplete()) {
if (isException()) {
action.run();
}
return this;
}
subscribe(new SimpleCallback() {
@Override
public void accept(T result, @Nullable Throwable e) {
if (e != null) {
action.run();
}
}
@Override
public String toString() {
return ".whenException(" + formatToString(action) + ')';
}
});
return this;
}
private static final Object NO_RESULT = new Object();
@NotNull
@Override
public Promise async() {
if (isComplete()) {
SettablePromise promise = new SettablePromise<>();
getCurrentEventloop().post(wrapContext(promise,
exception == null ?
() -> promise.set(result) :
() -> promise.setException(exception)));
return promise;
}
return this;
}
@NotNull
@Override
public Promise combine(@NotNull Promise extends U> other, @NotNull BiFunction super T, ? super U, ? extends V> fn) {
if (this.isComplete()) {
if (this.isResult()) {
return (Promise) other
.map(otherResult -> fn.apply(this.getResult(), otherResult))
.whenException(() -> Recyclers.recycle(this.getResult()));
}
other.whenResult(Recyclers::recycle);
return (Promise) this;
}
if (other.isComplete()) {
if (other.isResult()) {
return (Promise) this
.map(result -> fn.apply(result, other.getResult()))
.whenException(() -> Recyclers.recycle(other.getResult()));
}
this.whenResult(Recyclers::recycle);
return (Promise) other;
}
PromiseCombine resultPromise = new PromiseCombine<>(fn);
other.whenComplete(resultPromise::acceptOther);
subscribe(resultPromise);
return resultPromise;
}
@SuppressWarnings({"unchecked", "WeakerAccess"})
private static class PromiseCombine extends NextPromise {
final BiFunction super T, ? super U, ? extends V> fn;
@Nullable
T thisResult = (T) NO_RESULT;
@Nullable
U otherResult = (U) NO_RESULT;
PromiseCombine(BiFunction super T, ? super U, ? extends V> fn) {
this.fn = fn;
}
@Override
public void accept(T result, @Nullable Throwable e) {
if (e == null) {
if (otherResult != NO_RESULT) {
onBothResults(result, otherResult);
} else {
thisResult = result;
}
} else {
onAnyException(e);
}
}
public void acceptOther(U result, @Nullable Throwable e) {
if (e == null) {
if (thisResult != NO_RESULT) {
onBothResults(thisResult, result);
} else {
otherResult = result;
}
} else {
onAnyException(e);
}
}
void onBothResults(@Nullable T thisResult, @Nullable U otherResult) {
tryComplete(fn.apply(thisResult, otherResult));
}
void onAnyException(@NotNull Throwable e) {
if (tryCompleteExceptionally(e)) {
if (thisResult != NO_RESULT) Recyclers.recycle(thisResult);
if (otherResult != NO_RESULT) Recyclers.recycle(otherResult);
}
}
@Override
public String describe() {
return ".combine(" + formatToString(fn) + ')';
}
}
@NotNull
@Override
public Promise both(@NotNull Promise> other) {
if (this.isComplete()) {
if (this.isResult()) {
Recyclers.recycle(this.getResult());
return other.map(AbstractPromise::recycleToVoid);
}
other.whenResult(Recyclers::recycle);
return (Promise) this;
}
if (other.isComplete()) {
if (other.isResult()) {
Recyclers.recycle(other.getResult());
return this.map(AbstractPromise::recycleToVoid);
}
this.whenResult(Recyclers::recycle);
return (Promise) other;
}
PromiseBoth