CallResult.java
/*
* Original work Copyright 2020 Vavr, http://vavr.io
* Original licence :
* Copyright 2020 Vavr, http://vavr.io
*
* 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, callResult express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
* Modified work in 2021 by Alexis "Bad_Pop" Vachard
* CallResult is a simplified version of the vavr Either adapted for JCoinbase's needs.
* For more information, please take a look at the https://www.vavr.io/
*/
package com.github.badpop.jcoinbase.control;
import io.vavr.collection.Iterator;
import io.vavr.collection.Seq;
import io.vavr.control.Either;
import io.vavr.control.Option;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serial;
import java.io.Serializable;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import static lombok.AccessLevel.PRIVATE;
/**
* Call result is a simplified version of the vavr {@link Either} adapted to the JCoinbase needs.
* For more information, please take a look at the <a href="https://www.vavr.io/">vavr site</a>
*
* <p>CallResult represents a value of two possible types. A CallResult is callResult a {@link
* Failure} or a {@link Success}.
*
* <p>If the given CallResult is a Success and projected to a Failure, the Failure operations have
* no effect on the Success value.<br>
* If the given CallResult is a Failure and projected to a Success, the Success operations have no
* effect on the Failure value.<br>
* If a Failure is projected to a Failure or a Success is projected to a Success, the operations
* have an effect.
*
* <p><strong>Example:</strong> A compute() function, which results callResult in an Integer value
* (in the case of success) or in an error message of type String (in the case of failure). By
* convention the success case is Success and the failure is Failure.
*
* <pre>
* <code>
* CallResult<String,Integer> value = compute().success().map(i -> i * 2).toCallResult();
* </code>
* </pre>
*
* If the result of compute() is Success(1), the value is Success(2).<br>
* If the result of compute() is Failure("error"), the value is Failure("error").
*
* @param <L> The type of the Failure value of an CallResult.
* @param <R> The type of the Success value of an CallResult.
*/
@ToString
@NoArgsConstructor(access = PRIVATE)
@SuppressWarnings({"java:S1948", "java:S1905", "unchecked"})
public abstract class CallResult<L, R> implements Iterable<R>, FunctionalValue<R>, Serializable {
@Serial private static final long serialVersionUID = 1L;
private static final String FAILURE_MAPPER_IS_NULL = "failureMapper is null";
private static final String FUNCTION_IS_NULL = "function is null";
private static final String OTHER_IS_NULL = "function is null";
private static final String ACTION_IS_NULL = "function is null";
private static final String EXCEPTION_FUNCTION_IS_NULL = "exceptionFunction is null";
private static final String MAPPER_IS_NULL = "mapper is null";
private static final String PREDICATE_IS_NULL = "predicate is null";
private static final String SUPPLIER_IS_NULL = "supplier is null";
/**
* Constructs a {@link Success}
*
* <pre>{@code
* // Creates CallResult instance initiated with success value 1
* CallResult<?, Integer> callResult = CallResult.success(1);
* }</pre>
*
* @param success The value.
* @param <L> Type of failure value.
* @param <R> Type of success value.
* @return A new {@code Success} instance.
*/
public static <L, R> CallResult<L, R> success(R success) {
return new Success<>(success);
}
/**
* Constructs a {@link Failure}
*
* <pre>{@code
* // Creates CallResult instance initiated with failure value "error message"
* CallResult<String, ?> callResult = CallResult.failure("error message");
* }</pre>
*
* @param failure The value.
* @param <L> Type of failure value.
* @param <R> Type of success value.
* @return A new {@code Failure} instance.
*/
public static <L, R> CallResult<L, R> failure(L failure) {
return new Failure<>(failure);
}
/**
* Narrows a widened {@code CallResult<? extends L, ? extends R>} to {@code CallResult<L, R>} by
* performing a type-safe cast. This is eligible because immutable/read-only collections are
* covariant.
*
* <pre>{@code
* // It's ok, Integer inherits from Number
* CallResult<?, Number> answer = CallResult.success(42);
*
* // RuntimeException is an Exception
* CallResult<Exception, ?> failed = CallResult.failure(new RuntimeException("poetry recital"));
* }</pre>
*
* @param callResult A {@code CallResult}.
* @param <L> Type of failure value.
* @param <R> Type of success value.
* @return the given {@code callResult} instance as narrowed type {@code CallResult<L, R>}.
*/
public static <L, R> CallResult<L, R> narrow(CallResult<? extends L, ? extends R> callResult) {
return (CallResult<L, R>) callResult;
}
/**
* Reduces many {@code CallResult}s into a single {@code CallResult} by transforming an {@code
* Iterable<CallResult<L, R>>} into a {@code CallResult<Seq<L>, Seq<R>>}.
*
* <p>If any of the given {@code CallResult}s is a {@link Failure} then {@code sequence} returns a
* {@link Failure} containing a non-empty {@link Seq} of all failure values.
*
* <p>If none of the given {@code CallResult}s is a {@link Failure} then {@code sequence} returns
* a {@link Success} containing a (possibly empty) {@link Seq} of all success values.
*
* <pre>{@code
* // = Success(Seq())
* CallResult.sequence(List.empty())
*
* // = Success(Seq(1, 2))
* CallResult.sequence(List.of(CallResult.success(1), CallResult.success(2)))
*
* // = Failure(Seq("x"))
* CallResult.sequence(List.of(CallResult.success(1), CallResult.failure("x")))
* }</pre>
*
* @param callResults An {@link Iterable} of {@code CallResult}s
* @param <L> closure of all failure types of the given {@code CallResult}s
* @param <R> closure of all success types of the given {@code CallResult}s
* @return An {@code CallResult} of a {@link Seq} of failure or success values
* @throws NullPointerException if {@code callResults} is null
*/
public static <L, R> CallResult<Seq<L>, Seq<R>> sequence(
Iterable<? extends CallResult<? extends L, ? extends R>> callResults) {
Objects.requireNonNull(callResults, "callResults is null");
return Iterator.ofAll((Iterable<CallResult<L, R>>) callResults)
.partition(CallResult::isFailure)
.apply(
(failurePartition, successPartition) ->
failurePartition.hasNext()
? CallResult.failure(failurePartition.map(CallResult::getFailure).toVector())
: CallResult.success(successPartition.map(CallResult::get).toVector()));
}
/**
* Returns the failure value.
*
* <pre>{@code
* // prints "error"
* System.out.println(CallResult.failure("error").getFailure());
*
* // throws NoSuchElementException
* System.out.println(CallResult.success(42).getFailure());
* }</pre>
*
* @return The failure value.
* @throws NoSuchElementException if this is a {@code Success}.
*/
public abstract L getFailure();
/**
* Returns whether this CallResult is a Failure.
*
* <pre>{@code
* // prints "true"
* System.out.println(CallResult.failure("error").isFailure());
*
* // prints "false"
* System.out.println(CallResult.success(42).isFailure());
* }</pre>
*
* @return true, if this is a Failure, false otherwise
*/
public abstract boolean isFailure();
/**
* Returns whether this CallResult is a Success.
*
* <pre>{@code
* // prints "true"
* System.out.println(CallResult.success(42).isSuccess());
*
* // prints "false"
* System.out.println(CallResult.failure("error").isSuccess());
* }</pre>
*
* @return true, if this is a Success, false otherwise
*/
public abstract boolean isSuccess();
/**
* Returns a FailureProjection of this CallResult.
*
* @return a new FailureProjection of this
*/
public final CallResult.FailureProjection<L, R> failure() {
return new CallResult.FailureProjection<>(this);
}
/**
* Returns a SuccessProjection of this CallResult.
*
* @return a new SuccessProjection of this
*/
public final CallResult.SuccessProjection<L, R> success() {
return new CallResult.SuccessProjection<>(this);
}
/**
* Maps callResult the failure or the success side of this disjunction.
*
* <pre>{@code
* CallResult<?, AtomicInteger> success = CallResult.success(new AtomicInteger(42));
*
* // prints "Success(42)"
* System.out.println(success.bimap(Function1.identity(), AtomicInteger::get));
*
* CallResult<Exception, ?> failure = CallResult.failure(new Exception("error"));
*
* // prints "Failure(error)"
* System.out.println(failure.bimap(Exception::getMessage, Function1.identity()));
* }</pre>
*
* @param failureMapper maps the failure value if this is a Failure
* @param successMapper maps the success value if this is a Success
* @param <X> The new failure type of the resulting CallResult
* @param <Y> The new success type of the resulting CallResult
* @return A new CallResult instance
*/
public final <X, Y> CallResult<X, Y> bimap(
Function<? super L, ? extends X> failureMapper,
Function<? super R, ? extends Y> successMapper) {
Objects.requireNonNull(failureMapper, FAILURE_MAPPER_IS_NULL);
Objects.requireNonNull(successMapper, "successMapper is null");
if (isSuccess()) {
return new Success<>(successMapper.apply(get()));
} else {
return new Failure<>(failureMapper.apply(getFailure()));
}
}
/**
* Folds callResult the failure or the success side of this disjunction.
*
* <pre>{@code
* CallResult<Exception, Integer> success = CallResult.success(3);
*
* // prints "Users updated: 3"
* System.out.println(success.fold(Exception::getMessage, count -> "Users updated: " + count));
*
* CallResult<Exception, Integer> failure = CallResult.failure(new Exception("Failed to update users"));
*
* // prints "Failed to update users"
* System.out.println(failure.fold(Exception::getMessage, count -> "Users updated: " + count));
* }</pre>
*
* @param failureMapper maps the failure value if this is a Failure
* @param successMapper maps the success value if this is a Success
* @param <U> type of the folded value
* @return A value of type U
*/
public final <U> U fold(
Function<? super L, ? extends U> failureMapper,
Function<? super R, ? extends U> successMapper) {
Objects.requireNonNull(failureMapper, FAILURE_MAPPER_IS_NULL);
Objects.requireNonNull(successMapper, "successMapper is null");
if (isSuccess()) {
return successMapper.apply(get());
} else {
return failureMapper.apply(getFailure());
}
}
/**
* Transforms this {@code CallResult}.
*
* <pre>{@code
* // prints "Answer is 42"
* System.out.println(CallResult.success(42).<String> transform(e -> "Answer is " + e.get()));
* }</pre>
*
* @param f A transformation
* @param <U> Type of transformation result
* @return An instance of type {@code U}
* @throws NullPointerException if {@code f} is null
*/
public final <U> U transform(Function<? super CallResult<L, R>, ? extends U> f) {
Objects.requireNonNull(f, FUNCTION_IS_NULL);
return f.apply(this);
}
/**
* Gets the Success value or an alternate value, if the projected CallResult is a Failure.
*
* <pre>{@code
* // prints "42"
* System.out.println(CallResult.success(42).getOrElseGet(l -> -1));
*
* // prints "13"
* System.out.println(CallResult.failure("error message").getOrElseGet(String::length));
* }</pre>
*
* @param other a function which converts a Failure value to an alternative Success value
* @return the success value, if the underlying CallResult is a Success or else the alternative
* Success value provided by {@code other} by applying the Failure value.
*/
public final R getOrElseGet(Function<? super L, ? extends R> other) {
Objects.requireNonNull(other, OTHER_IS_NULL);
if (isSuccess()) {
return get();
} else {
return other.apply(getFailure());
}
}
/**
* Runs an action in the case this is a projection on a Failure value.
*
* <pre>{@code
* // prints "no value found"
* CallResult.failure("no value found").orElseRun(System.out::println);
* }</pre>
*
* @param action an action which consumes a Failure value
*/
public final void orElseRun(Consumer<? super L> action) {
Objects.requireNonNull(action, ACTION_IS_NULL);
if (isFailure()) {
action.accept(getFailure());
}
}
/**
* Gets the Success value or throws, if the projected CallResult is a Failure.
*
* <pre>{@code
* Function<String, RuntimeException> exceptionFunction = RuntimeException::new;
* // prints "42"
* System.out.println(CallResult.<String, Integer>success(42).getOrElseThrow(exceptionFunction));
*
* // throws RuntimeException("no value found")
* CallResult.failure("no value found").getOrElseThrow(exceptionFunction);
* }</pre>
*
* @param <X> a throwable type
* @param exceptionFunction a function which creates an exception based on a Failure value
* @return the success value, if the underlying CallResult is a Success or else throws the
* exception provided by {@code exceptionFunction} by applying the Failure value.
* @throws X if the projected CallResult is a Failure
*/
public final <X extends Throwable> R getOrElseThrow(Function<? super L, X> exceptionFunction)
throws X {
Objects.requireNonNull(exceptionFunction, EXCEPTION_FUNCTION_IS_NULL);
if (isSuccess()) {
return get();
} else {
throw exceptionFunction.apply(getFailure());
}
}
/**
* Converts a {@code Failure} to a {@code Success} vice versa by wrapping the value in a new type.
*
* <pre>{@code
* // prints "Success(42)"
* System.out.println(CallResult.failure(42).swap());
*
* // prints "Failure(message)"
* System.out.println(CallResult.success("message").swap());
* }</pre>
*
* @return a new {@code CallResult}
*/
public final CallResult<R, L> swap() {
if (isSuccess()) {
return new Failure<>(get());
} else {
return new Success<>(getFailure());
}
}
/**
* Calls recoveryFunction if the projected CallResult is a Failure, performs no operation if this
* is a Success. This is similar to {@code getOrElseGet}, but where the fallback method also
* returns an CallResult.
*
* <pre>{@code
* CallResult<Integer, String> tryGetString() { return CallResult.failure(1); }
*
* CallResult<Integer, String> tryGetStringAnotherWay(Integer lvalue) { return CallResult.success("yo " + lvalue); }
*
* = Success("yo 1")
* tryGetString().recover(this::tryGetStringAnotherWay);
* }</pre>
*
* @param recoveryFunction a function which accepts a Failure value and returns an CallResult
* @return an {@code CallResult<L, R>} instance
* @throws NullPointerException if the given {@code recoveryFunction} is null
*/
public final CallResult<L, R> recoverWith(
Function<? super L, ? extends CallResult<? extends L, ? extends R>> recoveryFunction) {
Objects.requireNonNull(recoveryFunction, "recoveryFunction is null");
if (isFailure()) {
return (CallResult<L, R>) recoveryFunction.apply(getFailure());
} else {
return this;
}
}
/**
* Calls {@code recoveryFunction} if the projected CallResult is a Failure, or returns {@code
* this} if Success. The result of {@code recoveryFunction} will be projected as a Success.
*
* <pre>{@code
* CallResult<Integer, String> tryGetString() { return CallResult.failure(1); }
*
* String getStringAnotherWay() { return "yo"; }
*
* = Success("yo")
* tryGetString().recover(this::getStringAnotherWay);
* }</pre>
*
* @param recoveryFunction a function which accepts a Failure value and returns a Success value
* @return an {@code CallResult<L, R>} instance
* @throws NullPointerException if the given {@code recoveryFunction} is null
*/
public final CallResult<L, R> recover(Function<? super L, ? extends R> recoveryFunction) {
Objects.requireNonNull(recoveryFunction, "recoveryFunction is null");
if (isFailure()) {
return CallResult.success(recoveryFunction.apply(getFailure()));
} else {
return this;
}
}
/**
* FlatMaps this success-biased CallResult.
*
* <pre>{@code
* // prints "Success(42)"
* System.out.println(CallResult.success(21).flatMap(v -> CallResult.success(v * 2)));
*
* // prints "Failure(error message)"
* System.out.println(CallResult.failure("error message").flatMap(CallResult::success));
* }</pre>
*
* @param mapper A mapper
* @param <U> Component type of the mapped success value
* @return this as {@code CallResult<L, U>} if this is a Failure, otherwise the success mapping
* result
* @throws NullPointerException if {@code mapper} is null
*/
public final <U> CallResult<L, U> flatMap(
Function<? super R, ? extends CallResult<L, ? extends U>> mapper) {
Objects.requireNonNull(mapper, MAPPER_IS_NULL);
if (isSuccess()) {
return (CallResult<L, U>) mapper.apply(get());
} else {
return (CallResult<L, U>) this;
}
}
/**
* Maps the value of this CallResult if it is a Success, performs no operation if this is a
* Failure.
*
* <pre>{@code
* // = Success("A")
* CallResult.success("a").map(String::toUpperCase);
*
* // = Failure(1)
* CallResult.failure(1).map(String::toUpperCase);
* }</pre>
*
* @param mapper A mapper
* @param <U> Component type of the mapped success value
* @return a mapped {@code Monad}
* @throws NullPointerException if {@code mapper} is null
*/
@Override
public final <U> CallResult<L, U> map(Function<? super R, ? extends U> mapper) {
Objects.requireNonNull(mapper, MAPPER_IS_NULL);
if (isSuccess()) {
return CallResult.success(mapper.apply(get()));
} else {
return (CallResult<L, U>) this;
}
}
/**
* Maps the value of this CallResult if it is a Failure, performs no operation if this is a
* Success.
*
* <pre>{@code
* // = Failure(2)
* CallResult.failure(1).mapLeft(i -> i + 1);
*
* // = Success("a")
* CallResult.success("a").mapLeft(i -> i + 1);
* }</pre>
*
* @param leftMapper A mapper
* @param <U> Component type of the mapped right value
* @return a mapped {@code Monad}
* @throws NullPointerException if {@code mapper} is null
*/
public final <U> CallResult<U, R> mapLeft(Function<? super L, ? extends U> leftMapper) {
Objects.requireNonNull(leftMapper, "leftMapper is null");
if (isFailure()) {
return CallResult.failure(leftMapper.apply(getFailure()));
} else {
return (CallResult<U, R>) this;
}
}
/**
* Maps the value of this CallResult if it is a Failure, performs no operation if this is a
* Success.
*
* <pre>{@code
* // = Failure(2)
* CallResult.failure(1).mapFailure(i -> i + 1);
*
* // = Success("a")
* CallResult.success("a").mapFailure(i -> i + 1);
* }</pre>
*
* @param failureMapper A mapper
* @param <U> Component type of the mapped success value
* @return a mapped {@code Monad}
* @throws NullPointerException if {@code mapper} is null
*/
public final <U> CallResult<U, R> mapFailure(Function<? super L, ? extends U> failureMapper) {
Objects.requireNonNull(failureMapper, FAILURE_MAPPER_IS_NULL);
if (isFailure()) {
return CallResult.failure(failureMapper.apply(getFailure()));
} else {
return (CallResult<U, R>) this;
}
}
/**
* Filters this success-biased {@code CallResult} by testing a predicate.
*
* <p>
*
* @param predicate A predicate
* @return a new {@code Option} instance
* @throws NullPointerException if {@code predicate} is null
*/
public final Option<CallResult<L, R>> filter(Predicate<? super R> predicate) {
Objects.requireNonNull(predicate, PREDICATE_IS_NULL);
return isFailure() || predicate.test(get()) ? Option.some(this) : Option.none();
}
/**
* Filters this success-biased {@code CallResult} by testing a predicate.
*
* @param predicate A predicate
* @return a new {@code CallResult}
* @throws NullPointerException if {@code predicate} is null
*/
public final Option<CallResult<L, R>> filterNot(Predicate<? super R> predicate) {
Objects.requireNonNull(predicate, PREDICATE_IS_NULL);
return filter(predicate.negate());
}
/**
* Filters this success-biased {@code CallResult} by testing a predicate. If the {@code
* CallResult} is a {@code Success} and the predicate doesn't match, the {@code CallResult} will
* be turned into a {@code Failure} with contents computed by applying the zero function to the
* {@code CallResult} value.
*
* <pre>{@code
* // = Failure("bad: a")
* CallResult.success("a").filterOrElse(i -> false, val -> "bad: " + val);
*
* // = Success("a")
* CallResult.success("a").filterOrElse(i -> true, val -> "bad: " + val);
* }</pre>
*
* @param predicate A predicate
* @param zero A function that turns a success value into a failure value if the success value
* does not make it through the filter.
* @return an {@code CallResult} instance
* @throws NullPointerException if {@code predicate} is null
*/
public final CallResult<L, R> filterOrElse(
Predicate<? super R> predicate, Function<? super R, ? extends L> zero) {
Objects.requireNonNull(predicate, PREDICATE_IS_NULL);
Objects.requireNonNull(zero, "zero is null");
if (isFailure() || predicate.test(get())) {
return this;
} else {
return CallResult.failure(zero.apply(get()));
}
}
@Override
public final boolean isEmpty() {
return isFailure();
}
public final CallResult<L, R> orElse(CallResult<? extends L, ? extends R> other) {
Objects.requireNonNull(other, OTHER_IS_NULL);
return isSuccess() ? this : (CallResult<L, R>) other;
}
public final CallResult<L, R> orElse(
Supplier<? extends CallResult<? extends L, ? extends R>> supplier) {
Objects.requireNonNull(supplier, SUPPLIER_IS_NULL);
return isSuccess() ? this : (CallResult<L, R>) supplier.get();
}
@Override
public final Iterator<R> iterator() {
if (isSuccess()) {
return Iterator.of(get());
} else {
return Iterator.empty();
}
}
/**
* Performs the given {@code failureAction} on the failure element if this is Failure. Performs
* the given {@code successAction} on the success element if this is Success.
*
* @param failureAction The action that will be performed on the failure element
* @param successAction The action that will be performed on the success element
* @return this instance
*/
public final CallResult<L, R> peek(
Consumer<? super L> failureAction, Consumer<? super R> successAction) {
Objects.requireNonNull(failureAction, "failureAction is null");
Objects.requireNonNull(successAction, "successAction is null");
if (isFailure()) {
failureAction.accept(getFailure());
} else { // this isSuccess() by definition
successAction.accept(get());
}
return this;
}
@Override
public final CallResult<L, R> peek(Consumer<? super R> action) {
Objects.requireNonNull(action, ACTION_IS_NULL);
if (isSuccess()) {
action.accept(get());
}
return this;
}
public final CallResult<L, R> peekFailure(Consumer<? super L> action) {
Objects.requireNonNull(action, ACTION_IS_NULL);
if (isFailure()) {
action.accept(getFailure());
}
return this;
}
/**
* A failure projection of a CallResult.
*
* @param <L> The type of the Failure value of a CallResult.
* @param <R> The type of the Success value of a CallResult.
*/
@ToString
public static final class FailureProjection<L, R> implements FunctionalValue<L> {
private final CallResult<L, R> callResult;
private FailureProjection(CallResult<L, R> callResult) {
this.callResult = callResult;
}
@Override
public boolean isEmpty() {
return callResult.isSuccess();
}
/**
* Gets the {@code Failure} value or throws.
*
* @return the failure value, if the underlying {@code CallResult} is a {@code Failure}
* @throws NoSuchElementException if the underlying {@code CallResult} of this {@code
* FailureProjection} is a {@code Success}
*/
@Override
public L get() {
if (callResult.isFailure()) {
return callResult.getFailure();
} else {
throw new NoSuchElementException("FailureProjection.get() on Success");
}
}
public CallResult.FailureProjection<L, R> orElse(
CallResult.FailureProjection<? extends L, ? extends R> other) {
Objects.requireNonNull(other, OTHER_IS_NULL);
return callResult.isFailure() ? this : (CallResult.FailureProjection<L, R>) other;
}
public CallResult.FailureProjection<L, R> orElse(
Supplier<? extends CallResult.FailureProjection<? extends L, ? extends R>> supplier) {
Objects.requireNonNull(supplier, SUPPLIER_IS_NULL);
return callResult.isFailure() ? this : (CallResult.FailureProjection<L, R>) supplier.get();
}
/**
* Gets the Failure value or an alternate value, if the projected CallResult is a Success.
*
* @param other an alternative value
* @return the failure value, if the underlying CallResult is a Failure or else {@code other}
* @throws NoSuchElementException if the underlying callResult of this FailureProjection is a
* Success
*/
@Override
public L getOrElse(L other) {
return callResult.isFailure() ? callResult.getFailure() : other;
}
/**
* Gets the Failure value or an alternate value, if the projected CallResult is a Success.
*
* @param other a function which converts a Success value to an alternative Failure value
* @return the failure value, if the underlying CallResult is a Failure or else the alternative
* Failure value provided by {@code other} by applying the Success value.
*/
public L getOrElseGet(Function<? super R, ? extends L> other) {
Objects.requireNonNull(other, OTHER_IS_NULL);
if (callResult.isFailure()) {
return callResult.getFailure();
} else {
return other.apply(callResult.get());
}
}
/**
* Runs an action in the case this is a projection on a Success value.
*
* @param action an action which consumes a Success value
*/
public void orElseRun(Consumer<? super R> action) {
Objects.requireNonNull(action, ACTION_IS_NULL);
if (callResult.isSuccess()) {
action.accept(callResult.get());
}
}
/**
* Gets the Failure value or throws, if the projected CallResult is a Success.
*
* @param <X> a throwable type
* @param exceptionFunction a function which creates an exception based on a Success value
* @return the failure value, if the underlying CallResult is a Failure or else throws the
* exception provided by {@code exceptionFunction} by applying the Success value.
* @throws X if the projected CallResult is a Success
*/
public <X extends Throwable> L getOrElseThrow(Function<? super R, X> exceptionFunction)
throws X {
Objects.requireNonNull(exceptionFunction, EXCEPTION_FUNCTION_IS_NULL);
if (callResult.isFailure()) {
return callResult.getFailure();
} else {
throw exceptionFunction.apply(callResult.get());
}
}
/**
* Returns the underlying callResult of this projection.
*
* @return the underlying callResult
*/
public CallResult<L, R> toCallResult() {
return callResult;
}
/**
* Returns {@code Some} value of type L if this is a failure projection of a Failure value and
* the predicate applies to the underlying value.
*
* @param predicate A predicate
* @return A new Option
*/
public Option<CallResult.FailureProjection<L, R>> filter(Predicate<? super L> predicate) {
Objects.requireNonNull(predicate, PREDICATE_IS_NULL);
return callResult.isSuccess() || predicate.test(callResult.getFailure())
? Option.some(this)
: Option.none();
}
/**
* FlatMaps this FailureProjection.
*
* @param mapper A mapper
* @param <U> Component type of the mapped failure value
* @return this as {@code FailureProjection<L, U>} if a Success is underlying, otherwise a the
* mapping result of the failure value.
* @throws NullPointerException if {@code mapper} is null
*/
public <U> CallResult.FailureProjection<U, R> flatMap(
Function<? super L, ? extends CallResult.FailureProjection<? extends U, R>> mapper) {
Objects.requireNonNull(mapper, MAPPER_IS_NULL);
if (callResult.isFailure()) {
return (CallResult.FailureProjection<U, R>) mapper.apply(callResult.getFailure());
} else {
return (CallResult.FailureProjection<U, R>) this;
}
}
/**
* Maps the failure value if the projected CallResult is a Failure.
*
* @param mapper A mapper which takes a failure value and returns a value of type U
* @param <U> The new type of a Failure value
* @return A new FailureProjection
*/
@Override
public <U> CallResult.FailureProjection<U, R> map(Function<? super L, ? extends U> mapper) {
Objects.requireNonNull(mapper, MAPPER_IS_NULL);
if (callResult.isFailure()) {
return callResult.mapFailure((Function<L, U>) mapper).failure();
} else {
return (CallResult.FailureProjection<U, R>) this;
}
}
/**
* Applies the given action to the value if the projected callResult is a Failure. Otherwise
* nothing happens.
*
* @param action An action which takes a failure value
* @return this FailureProjection
*/
@Override
public CallResult.FailureProjection<L, R> peek(Consumer<? super L> action) {
Objects.requireNonNull(action, ACTION_IS_NULL);
if (callResult.isFailure()) {
action.accept(callResult.getFailure());
}
return this;
}
/**
* Transforms this {@code FailureProjection}.
*
* @param f A transformation
* @param <U> Type of transformation result
* @return An instance of type {@code U}
* @throws NullPointerException if {@code f} is null
*/
public <U> U transform(Function<? super CallResult.FailureProjection<L, R>, ? extends U> f) {
Objects.requireNonNull(f, FUNCTION_IS_NULL);
return f.apply(this);
}
@Override
public Iterator<L> iterator() {
if (callResult.isFailure()) {
return Iterator.of(callResult.getFailure());
} else {
return Iterator.empty();
}
}
@Override
public boolean equals(Object obj) {
return (obj == this)
|| (obj instanceof CallResult.FailureProjection
&& Objects.equals(callResult, ((CallResult.FailureProjection<?, ?>) obj).callResult));
}
@Override
public int hashCode() {
return callResult.hashCode();
}
}
/**
* A success projection of a CallResult.
*
* @param <L> The type of the Failure value of a CallResult.
* @param <R> The type of the Success value of a CallResult.
*/
@ToString
public static final class SuccessProjection<L, R> implements FunctionalValue<R> {
private final CallResult<L, R> callResult;
private SuccessProjection(CallResult<L, R> callResult) {
this.callResult = callResult;
}
@Override
public boolean isEmpty() {
return callResult.isFailure();
}
/**
* Gets the {@code Success} value or throws.
*
* @return the success value, if the underlying {@code CallResult} is a {@code Success}
* @throws NoSuchElementException if the underlying {@code CallResult} of this {@code
* SuccessProjection} is a {@code Failure}
*/
@Override
public R get() {
if (callResult.isSuccess()) {
return callResult.get();
} else {
throw new NoSuchElementException("SuccessProjection.get() on Failure");
}
}
public CallResult.SuccessProjection<L, R> orElse(
CallResult.SuccessProjection<? extends L, ? extends R> other) {
Objects.requireNonNull(other, OTHER_IS_NULL);
return callResult.isSuccess() ? this : (CallResult.SuccessProjection<L, R>) other;
}
public CallResult.SuccessProjection<L, R> orElse(
Supplier<? extends CallResult.SuccessProjection<? extends L, ? extends R>> supplier) {
Objects.requireNonNull(supplier, SUPPLIER_IS_NULL);
return callResult.isSuccess() ? this : (CallResult.SuccessProjection<L, R>) supplier.get();
}
/**
* Gets the Success value or an alternate value, if the projected CallResult is a Failure.
*
* @param other an alternative value
* @return the success value, if the underlying CallResult is a Success or else {@code other}
* @throws NoSuchElementException if the underlying callResult of this SuccessProjection is a
* Failure
*/
@Override
public R getOrElse(R other) {
return callResult.getOrElse(other);
}
/**
* Gets the Success value or an alternate value, if the projected CallResult is a Failure.
*
* <pre>{@code
* // prints 42
* System.out.println(CallResult.success(42).getOrElseGet(l -> 2));
* // prints 0
* System.out.println(CallResult.failure(42).getOrElseGet(l -> 0));
* }</pre>
*
* @param other a function which converts a Failure value to an alternative Success value
* @return the success value, if the underlying CallResult is a Success or else the alternative
* Success value provided by {@code other} by applying the Failure value.
*/
public R getOrElseGet(Function<? super L, ? extends R> other) {
Objects.requireNonNull(other, OTHER_IS_NULL);
return callResult.getOrElseGet(other);
}
/**
* Runs an action in the case this is a projection on a Failure value.
*
* <pre>{@code
* // nothing is printed
* CallResult.success(42).orElseRun(System.out::println);
*
* // prints "error message"
* CallResult.failure("error message").orElseRun(System.out::println);
* }</pre>
*
* @param action an action which consumes a Failure value
*/
public void orElseRun(Consumer<? super L> action) {
Objects.requireNonNull(action, ACTION_IS_NULL);
callResult.orElseRun(action);
}
/**
* Gets the Success value or throws, if the projected CallResult is a Failure.
*
* <pre>{@code
* // prints "42"
* System.out.println(CallResult.<String, Integer> success(42).getOrElseThrow(s -> new RuntimeException(s)));
*
* // throws RuntimeException("error message")
* CallResult.failure("error message").getOrElseThrow(s -> new RuntimeException(s));
* }</pre>
*
* @param <X> a throwable type
* @param exceptionFunction a function which creates an exception based on a Failure value
* @return the success value, if the underlying CallResult is a Success or else throws the
* exception provided by {@code exceptionFunction} by applying the Failure value.
* @throws X if the projected CallResult is a Failure
*/
public <X extends Throwable> R getOrElseThrow(Function<? super L, X> exceptionFunction)
throws X {
Objects.requireNonNull(exceptionFunction, EXCEPTION_FUNCTION_IS_NULL);
return callResult.getOrElseThrow(exceptionFunction);
}
/**
* Returns the underlying callResult of this projection.
*
* @return the underlying callResult
*/
public CallResult<L, R> toCallResult() {
return callResult;
}
/**
* Returns {@code Some} value of type R if this is a success projection of a Success value and
* the predicate applies to the underlying value.
*
* @param predicate A predicate
* @return A new Option
*/
public Option<CallResult.SuccessProjection<L, R>> filter(Predicate<? super R> predicate) {
Objects.requireNonNull(predicate, PREDICATE_IS_NULL);
return callResult.isFailure() || predicate.test(callResult.get())
? Option.some(this)
: Option.none();
}
/**
* FlatMaps this SuccessProjection.
*
* @param mapper A mapper
* @param <U> Component type of the mapped success value
* @return this as {@code SuccessProjection<L, U>} if a Failure is underlying, otherwise a the
* mapping result of the success value.
* @throws NullPointerException if {@code mapper} is null
*/
public <U> CallResult.SuccessProjection<L, U> flatMap(
Function<? super R, ? extends CallResult.SuccessProjection<L, ? extends U>> mapper) {
Objects.requireNonNull(mapper, MAPPER_IS_NULL);
if (callResult.isSuccess()) {
return (CallResult.SuccessProjection<L, U>) mapper.apply(callResult.get());
} else {
return (CallResult.SuccessProjection<L, U>) this;
}
}
/**
* Maps the success value if the projected CallResult is a Success.
*
* @param mapper A mapper which takes a success value and returns a value of type U
* @param <U> The new type of a Success value
* @return A new SuccessProjection
*/
@Override
public <U> CallResult.SuccessProjection<L, U> map(Function<? super R, ? extends U> mapper) {
Objects.requireNonNull(mapper, MAPPER_IS_NULL);
if (callResult.isSuccess()) {
return callResult.map((Function<R, U>) mapper).success();
} else {
return (CallResult.SuccessProjection<L, U>) this;
}
}
/**
* Applies the given action to the value if the projected callResult is a Success. Otherwise
* nothing happens.
*
* @param action An action which takes a success value
* @return this {@code CallResult} instance
*/
@Override
public CallResult.SuccessProjection<L, R> peek(Consumer<? super R> action) {
Objects.requireNonNull(action, ACTION_IS_NULL);
if (callResult.isSuccess()) {
action.accept(callResult.get());
}
return this;
}
/**
* Transforms this {@code SuccessProjection}.
*
* @param f A transformation
* @param <U> Type of transformation result
* @return An instance of type {@code U}
* @throws NullPointerException if {@code f} is null
*/
public <U> U transform(Function<? super CallResult.SuccessProjection<L, R>, ? extends U> f) {
Objects.requireNonNull(f, FUNCTION_IS_NULL);
return f.apply(this);
}
@Override
public Iterator<R> iterator() {
return callResult.iterator();
}
@Override
public boolean equals(Object obj) {
return (obj == this)
|| (obj instanceof CallResult.SuccessProjection
&& Objects.equals(callResult, ((CallResult.SuccessProjection<?, ?>) obj).callResult));
}
@Override
public int hashCode() {
return callResult.hashCode();
}
}
/**
* The {@code Failure} version of a {@code CallResult}.
*
* @param <L> failure component type
* @param <R> success component type
*/
@ToString
@SuppressWarnings("java:S1948")
public static final class Failure<L, R> extends CallResult<L, R> implements Serializable {
@Serial private static final long serialVersionUID = 1L;
private final L value;
/**
* Constructs a {@code Failure}.
*
* @param value a failure value
*/
private Failure(L value) {
this.value = value;
}
@Override
public R get() {
throw new NoSuchElementException("get() on Failure");
}
@Override
public boolean isSuccess() {
return false;
}
@Override
public boolean isFailure() {
return true;
}
@Override
public L getFailure() {
return value;
}
@Override
public boolean equals(Object obj) {
return (obj == this)
|| (obj instanceof CallResult.Failure
&& Objects.equals(value, ((Failure<?, ?>) obj).value));
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
}
/**
* The {@code Success} version of a {@code CallResult}.
*
* @param <L> failure component type
* @param <R> success component type
*/
@ToString
public static final class Success<L, R> extends CallResult<L, R> implements Serializable {
@Serial private static final long serialVersionUID = 1L;
private final R value;
/**
* Constructs a {@code Success}.
*
* @param value a success value
*/
private Success(R value) {
this.value = value;
}
@Override
public R get() {
return value;
}
@Override
public L getFailure() {
throw new NoSuchElementException("getFailure() on Success");
}
@Override
public boolean isFailure() {
return false;
}
@Override
public boolean isSuccess() {
return true;
}
@Override
public boolean equals(Object obj) {
return (obj == this)
|| (obj instanceof CallResult.Success
&& Objects.equals(value, ((Success<?, ?>) obj).value));
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
}
}