package it.tdlight.tdlibsession.td;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Error;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.concurrent.CompletionException;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
/**
* Encapsulates the result of an asynchronous operation.
*
* Many operations in Vert.x APIs provide results back by passing an instance of this in a {@link io.vertx.core.Handler}.
*
* The result can either have failed or succeeded.
*
* If it failed then the cause of the failure is available with {@link #cause}.
*
* If it succeeded then the actual result is available with {@link #result}
*
* @author Tim Fox
*/
public interface TdResult {
/**
* The result of the operation. This will be null if the operation failed.
*
* @return the result or null if the operation failed.
*/
T result();
/**
* The result of the operation. This will throw CompletionException if the operation failed.
*
* @return the result.
*/
T orElseThrow() throws CompletionException;
/**
* A TdApi.Error describing failure. This will be null if the operation succeeded.
*
* @return the cause or null if the operation succeeded.
*/
TdApi.Error cause();
/**
* Did it succeed?
*
* @return true if it succeded or false otherwise
*/
boolean succeeded();
/**
* Did it fail?
*
* @return true if it failed or false otherwise
*/
boolean failed();
/**
* Apply a {@code mapper} function on this async result.
*
* The {@code mapper} is called with the completed value and this mapper returns a value. This value will complete the result returned by this method call.
*
* When this async result is failed, the failure will be propagated to the returned async result and the {@code mapper} will not be called.
*
* @param mapper the mapper function
* @return the mapped async result
*/
default TdResult map(Function mapper) {
if (mapper == null) {
throw new NullPointerException();
}
return new TdResult() {
@Override
public U result() {
if (succeeded()) {
return mapper.apply(TdResult.this.result());
} else {
return null;
}
}
@Override
public U orElseThrow() throws CompletionException {
if (succeeded()) {
return mapper.apply(TdResult.this.orElseThrow());
} else {
return null;
}
}
@Override
public TdApi.Error cause() {
return TdResult.this.cause();
}
@Override
public boolean succeeded() {
return TdResult.this.succeeded();
}
@Override
public boolean failed() {
return TdResult.this.failed();
}
};
}
/**
* Map the result of this async result to a specific {@code value}.
*
* When this async result succeeds, this {@code value} will succeeed the async result returned by this method call.
*
* When this async result fails, the failure will be propagated to the returned async result.
*
* @param value the value that eventually completes the mapped async result
* @return the mapped async result
*/
default TdResult map(V value) {
return map((Function) t -> value);
}
/**
* Map the result of this async result to {@code null}.
*
* This is a convenience for {@code TdResult.map((T) null)} or {@code TdResult.map((Void) null)}.
*
* When this async result succeeds, {@code null} will succeeed the async result returned by this method call.
*
* When this async result fails, the failure will be propagated to the returned async result.
*
* @return the mapped async result
*/
default TdResult mapEmpty() {
return map((V)null);
}
/**
* Apply a {@code mapper} function on this async result.
*
* The {@code mapper} is called with the failure and this mapper returns a value. This value will complete the result returned by this method call.
*
* When this async result is succeeded, the value will be propagated to the returned async result and the {@code mapper} will not be called.
*
* @param mapper the mapper function
* @return the mapped async result
*/
default TdResult otherwise(Function mapper) {
if (mapper == null) {
throw new NullPointerException();
}
return new TdResult() {
@Override
public T result() {
if (TdResult.this.succeeded()) {
return TdResult.this.result();
} else if (TdResult.this.failed()) {
return mapper.apply(TdResult.this.cause());
} else {
return null;
}
}
@Override
public T orElseThrow() {
if (TdResult.this.succeeded()) {
return TdResult.this.orElseThrow();
} else if (TdResult.this.failed()) {
return mapper.apply(TdResult.this.cause());
} else {
return null;
}
}
@Override
public TdApi.Error cause() {
return null;
}
@Override
public boolean succeeded() {
return TdResult.this.succeeded() || TdResult.this.failed();
}
@Override
public boolean failed() {
return false;
}
};
}
/**
* Map the failure of this async result to a specific {@code value}.
*
* When this async result fails, this {@code value} will succeeed the async result returned by this method call.
*
* When this async succeeds, the result will be propagated to the returned async result.
*
* @param value the value that eventually completes the mapped async result
* @return the mapped async result
*/
default TdResult otherwise(T value) {
return otherwise(err -> value);
}
/**
* Map the failure of this async result to {@code null}.
*
* This is a convenience for {@code TdResult.otherwise((T) null)}.
*
* When this async result fails, the {@code null} will succeeed the async result returned by this method call.
*
* When this async succeeds, the result will be propagated to the returned async result.
*
* @return the mapped async result
*/
default TdResult otherwiseEmpty() {
return otherwise(err -> null);
}
static TdResult succeeded(@NotNull T value) {
Objects.requireNonNull(value);
return new TdResultImpl(value, null);
}
static TdResult failed(@NotNull TdApi.Error error) {
Objects.requireNonNull(error);
return new TdResultImpl(null, error);
}
static TdResult of(@NotNull TdApi.Object resultOrError) {
if (resultOrError.getConstructor() == TdApi.Error.CONSTRUCTOR) {
return failed((TdApi.Error) resultOrError);
} else {
//noinspection unchecked
return succeeded((T) resultOrError);
}
}
class TdResultImpl implements TdResult {
private final U value;
private final Error error;
public TdResultImpl(U value, Error error) {
this.value = value;
this.error = error;
assert (value == null) != (error == null);
}
@Override
public U result() {
return value;
}
@Override
public U orElseThrow() {
if (error != null) {
throw new TdError(error.code, error.message);
}
return value;
}
@Override
public Error cause() {
return error;
}
@Override
public boolean succeeded() {
return value != null;
}
@Override
public boolean failed() {
return error != null;
}
@Override
public String toString() {
return new StringJoiner(", ", TdResultImpl.class.getSimpleName() + "[", "]")
.add("value=" + value)
.add("error=" + error)
.toString();
}
}
}