
ru.progrm_jarvis.javacommons.object.Result Maven / Gradle / Ivy
package ru.progrm_jarvis.javacommons.object;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.Value;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.progrm_jarvis.javacommons.annotation.Any;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* A tagged union representing either a successful result or an error.
*
* @param type of successful result
* @param type of error result
*/
@SuppressWarnings("PublicInnerClass")
public interface Result extends Supplier {
/* ************************************************* Factories ************************************************* */
/**
* Creates a new successful result.
*
* @param value value of the successful result
* @param type of successful result
* @param type of error result
* @return created successful result
*/
static @NotNull Result success(final T value) {
return value == null ? nullSuccess() : new Success<>(value);
}
/**
* Creates a new successful result with {@code null} value.
*
* @param type of successful result
* @param type of error result
* @return created successful result
*/
@SuppressWarnings("unchecked")
static @NotNull Result<@Nullable T, E> nullSuccess() {
return (Result) NullSuccess.INSTANCE;
}
/**
* Creates a new error result.
*
* @param error value of the error result
* @param type of successful result
* @param type of error result
* @return created error result
*/
static @NotNull Result error(final E error) {
return error == null ? nullError() : new Error<>(error);
}
/**
* Creates a new {@code void}-error result.
*
* @param type of successful result
* @param type of error result
* @return created error result
*/
@SuppressWarnings("unchecked")
static @NotNull Result nullError() {
return (Result) NullError.INSTANCE;
}
/**
* Converts the given {@link Optional} into a {@link #nullError() null-error result}.
*
* @param optional optional to be converted into the result
* @param type of successful result
* @param type of error result
* @return {@link #success(Object) successful result} if the value {@link Optional#isPresent()} in the optional
* and a {@link #nullError() null-error result} otherwise
*
* @see #from(Optional, Supplier) alternative with customizable error value
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") // convertion from optional itself
static @NotNull Result from(final @NonNull Optional extends T> optional) {
return optional.>map(Result::success).orElseGet(Result::nullError);
}
/**
* Converts the given {@link Optional} into an {@link #error(Object) error result}.
*
* @param optional optional to be converted into the result
* @param errorSupplier supplier to create an error
* if the given {@link Optional} is {@link Optional#empty() empty}
* @param type of successful result
* @param type of error result
* @return {@link #success(Object) successful result} if the value {@link Optional#isPresent()} in the optional
* and an {@link #error(Object) error result} with an error supplied from {@code error supplier} otherwise
*
* @see #from(Optional) alternative with default (i.e. null) error
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") // convertion from optional itself
static @NotNull Result from(final @NonNull Optional extends T> optional,
final @NonNull Supplier extends E> errorSupplier) {
return optional.>map(Result::success).orElseGet(() -> error(errorSupplier.get()));
}
/**
* Creates a result by calling the specified callable.
*
* @param callable provider of the result
* @param type of the result provided by the given callable
* @return {@link #success(Object) successful result} if the callable ran unexceptionally
* and {@link #error(Object) error result} containing the thrown {@link Exception exception} otherwise
*/
static Result tryFrom(final @NonNull Callable extends T> callable) {
final T value;
try {
value = callable.call();
} catch (final Exception x) {
return error(x);
}
return success(value);
}
/* ********************************************** Checking methods ********************************************** */
/**
* Checks is this result is successful.
*
* @return {@code true} if this is a successful result and {@code false} otherwise
*/
boolean isSuccess();
/**
* Checks is this result is an error.
*
* @return {@code true} if this is an error result and {@code false} otherwise
*/
boolean isError();
/* ********************************************* Unwrapping methods ********************************************* */
@Override
default T get() {
return unwrap();
}
/**
* Gets the value of this result throwing a {@link NotSuccessException}
* if this is an {@link #isError() error result}.
*
* @return successful value of this result
*
* @throws NotSuccessException if this is an {@link #isError() error result}
* @see #expect(String) analog with exception message specification
* @see #orElseThrow(Function) analog with exception specification
* @see #orElseSneakyThrow(Function) analog with unchecked exception specification
*/
T unwrap();
/**
* Gets the value of this result throwing a {@link NotSuccessException}
* if this is an {@link #isError() error result}.
*
* @param message message to be specified to {@link NotSuccessException}
* @return successful value of this result
*
* @throws NotSuccessException if this is an {@link #isError() error result}
* @see #unwrap() analog with default message
* @see #orElseThrow(Function) analog with exception specification
* @see #orElseSneakyThrow(Function) analog with unchecked exception specification
*/
T expect(@NonNull String message);
/**
* Gets the value of this result throwing {@code X} got by using the specified supplier
* if this is an {@link #isError() error result}.
*
* @param exceptionFactory factory of a thrown exception consuming the error value of this result
* @param type of exception thrown if this is an {@link #isError() error result}
* @return successful value of this result
*
* @throws X if this is an {@link #isError() error result}
* @see #expect(String) {@link NotSuccessException} analog
* @see #unwrap() default message {@link NotSuccessException} analog
* @see #orElseSneakyThrow(Function) unchecked equivalent
*/
T orElseThrow(@NonNull Function super E, ? extends X> exceptionFactory) throws X;
/**
* Gets the value of this result throwing {@code X} got by using the specified supplier
* if this is an {@link #isError() error result}. Throws {@code X} if this is an {@link #isError() error result}.
* This differs from {@link #orElseThrow(Function)} as this does not declare {@code X} as a thrown exception.
*
* @param exceptionFactory factory of a thrown exception consuming the error value of this result
* @param type of exception thrown if this is an {@link #isError() error result}
* @return successful value of this result
*
* @see #expect(String) {@link NotSuccessException} analog
* @see #unwrap() default message {@link NotSuccessException} analog
* @see #orElseThrow(Function) checked equivalent
*/
@SneakyThrows
default T orElseSneakyThrow(
final @NonNull Function super E, ? extends X> exceptionFactory
) {
return orElseThrow(exceptionFactory);
}
/**
* Gets the value of this result if this is a {@link #isSuccess() successful}
* otherwise returning default value.
*
* @param defaultValue default value to be returned if this is an {@link #isError()} error value}
* @return successful value if this a {@link #isSuccess() successful result} or {@code defaultValue} otherwise
*/
T orDefault(T defaultValue);
/**
* Gets the value of this result if this is a {@link #isSuccess() successful}
* otherwise returning the value got by using the specified supplier.
*
* @param defaultValueSupplier supplier of the default value
* to be returned if this is an {@link #isError()} error value}
* @return successful value if this a {@link #isSuccess() successful result} or {@code defaultValue} otherwise
*/
T orGetDefault(@NonNull Supplier extends T> defaultValueSupplier);
/**
* Gets the error of this result throwing a {@link NotErrorException}
* if this is a {@link #isSuccess() successful result}.
*
* @return error value of this result
*
* @throws NotErrorException if this is a {@link #isSuccess() successful result}
* @see #expectError(String) analog with exception message specification
* @see #errorOrElseThrow(Function) analog with exception specification
* @see #errorOrElseSneakyThrow(Function) analog with unchecked exception specification
*/
E unwrapError();
/**
* Gets the error of this result throwing a {@link NotErrorException}
* if this is a {@link #isSuccess() successful result}.
*
* @param message message to be specified to {@link NotErrorException}
* @return error value of this result
*
* @throws NotErrorException if this is a {@link #isSuccess() successful result}
* @see #unwrapError() analog with default message
* @see #errorOrElseThrow(Function) analog with exception specification
* @see #errorOrElseSneakyThrow(Function) analog with unchecked exception specification
*/
E expectError(String message);
/**
* Gets the error of this result throwing {@code X} got by using the specified supplier
* if this is a {@link #isSuccess() successful result}.
*
* @param exceptionFactory factory of a thrown exception consuming the successful value of this result
* @param type of exception thrown if this is a {@link #isSuccess() successful result}
* @return error value of this result
*
* @throws X if this is an {@link #isError() error result}
* @see #unwrapError() default message {@link NotErrorException} analog
* @see #expectError(String) {@link NotErrorException} analog
* @see #errorOrElseSneakyThrow(Function) unchecked equivalent
*/
E errorOrElseThrow(@NonNull Function super T, ? extends X> exceptionFactory) throws X;
/**
* Gets the error of this result throwing {@code X} got by using the specified supplier
* if this is a {@link #isSuccess() successful result}. Throws {@code X} if this is an {@link #isError() error result}.
* This differs from {@link #orElseThrow(Function)} as this does not declare {@code X} as a thrown exception.
*
* @param exceptionFactory factory of a thrown exception consuming the successful value of this result
* @param type of exception thrown if this is a {@link #isSuccess() successful result}
* @return error value of this result
*
* @see #unwrapError() default message {@link NotErrorException} analog
* @see #expectError(String) {@link NotErrorException} analog
* @see #errorOrElseThrow(Function) checked equivalent
*/
@SneakyThrows
default E errorOrElseSneakyThrow(
final @NonNull Function super T, ? extends X> exceptionFactory
) {
return errorOrElseThrow(exceptionFactory);
}
/**
* Invokes the given function if this result is a {@link #isSuccess() successful result}.
*
* @param successConsumer consumer accepting the {@link T successful value}
*/
void ifSuccess(@NonNull Consumer super T> successConsumer);
/**
* Invokes the given function if this result is an {@link #isError() error result}.
*
* @param errorConsumer consumer accepting the {@link E error value}
*/
void ifError(@NonNull Consumer super E> errorConsumer);
/**
* Invokes the corresponding function depending on this result's type.
*
* @param successConsumer consumer accepting the {@link T successful value}
* @param errorConsumer consumer accepting the {@link E error value}
*/
void handle(@NonNull Consumer super T> successConsumer, @NonNull Consumer super E> errorConsumer);
/* ********************************************** Mapping methods ********************************************** */
/**
* Maps the result if it is {@link #isSuccess() successful} returning a new result with the result of mapping
* otherwise keeping the {@link #isError() error result}.
*
* @param mappingFunction function to map the successful result
* @param type of the resulting successful value
* @return mapped successful result if it was a {@link #isSuccess() successful result}
* or an error result if it was an {@link #isError() error result}
*/
@NotNull Result map(@NonNull Function super T, ? extends R> mappingFunction);
/**
* Consumes the result if it is {@link #isSuccess() successful} returning the same result.
*
* @param consumer consumer to accept the successful result
* @return the same result
*/
@NotNull Result peek(@NonNull Consumer super T> consumer);
/**
* Maps the result if it is an {@link #isError() error result} returning a new result with the result of mapping
* otherwise keeping the {@link #isError() error result}.
*
* @param mappingFunction function to map the error result
* @param type of the resulting error value
* @return mapped error result if it was an {@link #isError() an error result}
* or a successful result if it was a {@link #isError() successful result}
*/
@NotNull Result mapError(@NonNull Function super E, ? extends R> mappingFunction);
/**
* Consumes the result if it is an {@link #isError() error result} returning the same result.
*
* @param consumer consumer to accept the successful result
* @return the same result
*/
@NotNull Result peekError(@NonNull Consumer super E> consumer);
/**
* Returns the given result if this is a {@link #isSuccess() successful result}
* otherwise keeping the {@link #isError() error result}.
*
* @param nextResult result to be returned if this is a {@link #isSuccess() successful result}
* @param type of the resulting successful value
* @return {@code nextResult} if this is a {@link #isSuccess() successful result} or this error result otherwise
*
* @see #flatMap(Function) lazy analog
*/
@NotNull Result and(@NonNull Result nextResult);
/**
* Also known as {@code andThen}. Maps the result if this is a {@link #isSuccess() successful result}
* returning the result of mapping otherwise keeping the {@link #isError() error result}.
*
* @param type of the resulting successful value
* @param mapper function to create a new result from {@link #unwrap() current successful one}
* @return mapped {@link #unwrap() successful result} if this was {@link #isSuccess() the one}
* or this error result otherwise
*
* @see #and(Result) non-lazy analog
*/
@NotNull Result flatMap(@NonNull Function super T, ? extends @NotNull Result> mapper);
/**
* Returns this result if this is a {@link #isSuccess() successful result}
* otherwise returning the given result.
*
* @param alternateResult result to be returned if this is an {@link #isError() error result}
* @param type of the resulting error value
* @return successful result if this is {@link #isSuccess() the one} or {@code alternateResult} otherwise
*
* @see #orElse(Function) lazy analog
*/
@NotNull Result or(@NonNull Result alternateResult);
/**
* Maps the result if this is an {@link #isError() error result}
* returning the result of mapping otherwise keeping the {@link #isSuccess() successful result}.
*
* @param mapper function to create a new result from {@link #unwrapError() current error one}
* @param type of the resulting error value
* @return mapped {@link #unwrapError() error result} if this was {@link #isError() the one}
* or this successful result otherwise
*
* @see #or(Result) non-lazy analog
*/
@NotNull Result orElse(@NonNull Function super E, ? extends @NotNull Result> mapper);
/**
* Swaps this result making an {@link #error(Object) error result} from a {@link #isSuccess() successful result}
* and a {@link #success(Object) successful result} from an {@link #isError() error result}.
*
* @return {@link #error(Object) error result} if this was a {@link #isSuccess() successful result}
* and a {@link #success(Object) successful result} if this was an {@link #isError() error result}
*/
@NotNull Result swap();
/* ********************************************* Conversion methods ********************************************* */
/**
* Converts this result to an {@link Optional} of its successful value.
*
* @return {@link Optional optional} containing the successful result's value
* if this is a {@link #isSuccess() successful result}
* and an {@link Optional#empty() empty optional} otherwise
*/
@NotNull Optional asOptional();
/**
* Converts this result to an {@link Optional} of its error.
*
* @return {@link Optional optional} containing the error result's error
* if this is an {@link #isError() error result}
* and an {@link Optional#empty() empty optional} otherwise
*/
@NotNull Optional asErrorOptional();
/**
* Converts this result to a {@link ValueContainer} of its successful value.
*
* @return {@link ValueContainer value container} containing the successful result's value
* if this is a {@link #isSuccess() successful result}
* or an {@link ValueContainer#empty() empty value-container} otherwise
*/
@NotNull ValueContainer asValueContainer();
/**
* Converts this result to a {@link ValueContainer} of its error value.
*
* @return {@link ValueContainer value container} containing the error result's error
* if this is an {@link #isError() error result}
* or an {@link ValueContainer#empty() empty value-container} otherwise
*/
@NotNull ValueContainer asErrorValueContainer();
/**
* Representation of a {@link #isSuccess() successful} {@link Result result}.
*
* @param type of successful result
* @param type of error result
*/
@Value
class Success implements Result {
/**
* Value wrapped by this result
*/
T value;
/**
* Changes the error type of this result.
*
* @param target error type
* @return this result with changed error type
*/
@SuppressWarnings("unchecked")
private <@Any R> Result transmuteErrorType() {
return (Result) this;
}
//
@Override
public boolean isSuccess() {
return true;
}
@Override
public boolean isError() {
return false;
}
//
//
@Override
public T unwrap() {
return value;
}
@Override
public T expect(final @NonNull String message) {
return value;
}
@Override
public T orElseThrow(final @NonNull Function super E, ? extends X> exceptionFactory) {
return value;
}
@Override
public T orDefault(final T defaultValue) {
return value;
}
@Override
public T orGetDefault(final @NonNull Supplier extends T> defaultValueSupplier) {
return value;
}
@Override
public E unwrapError() {
throw new NotErrorException("This is not an error result");
}
@Override
public E expectError(final String message) {
throw new NotErrorException(message);
}
@Override
public E errorOrElseThrow(
final @NonNull Function super T, ? extends X> exceptionSupplier
) throws X {
throw exceptionSupplier.apply(value);
}
@Override
public void ifSuccess(final @NonNull Consumer super T> successConsumer) {
successConsumer.accept(value);
}
@Override
public void ifError(final @NonNull Consumer super E> errorConsumer) {}
@Override
public void handle(
final @NonNull Consumer super T> successConsumer,
final @NonNull Consumer super E> errorConsumer
) {
successConsumer.accept(value);
}
//
//
@Override
public @NotNull Result map(final @NonNull Function super T, ? extends R> mappingFunction) {
return success(mappingFunction.apply(value));
}
@Override
public @NotNull Result peek(final @NonNull Consumer super T> consumer) {
consumer.accept(value);
return this;
}
@Override
public @NotNull <@Any R> Result mapError(
final @NonNull Function super E, ? extends R> mappingFunction
) {
return transmuteErrorType();
}
@Override
public @NotNull Result peekError(final @NonNull Consumer super E> consumer) {
return this;
}
@Override
public @NotNull Result and(final @NonNull Result nextResult) {
return nextResult;
}
@Override
public @NotNull Result flatMap(
final @NonNull Function super T, ? extends @NotNull Result> mapper
) {
return mapper.apply(value);
}
@Override
public @NotNull <@Any R> Result or(final @NonNull Result alternateResult) {
return transmuteErrorType();
}
@Override
public @NotNull <@Any R> Result orElse(
final @NonNull Function super E, ? extends @NotNull Result> mapper
) {
return transmuteErrorType();
}
@Override
public @NotNull Result swap() {
return error(value);
}
//
//
@Override
public @NotNull Optional asOptional() {
return Optional.of(value);
}
@Override
public @NotNull Optional asErrorOptional() {
return Optional.empty();
}
@Override
public @NotNull ValueContainer asValueContainer() {
return ValueContainer.of(value);
}
@Override
public @NotNull ValueContainer asErrorValueContainer() {
return ValueContainer.empty();
}
//
}
/**
* Representation of an {@link #isError() error} {@link Result result}.
*
* @param type of successful result
* @param type of error result
*/
@Value
class Error<@Any T, E> implements Result {
/**
* Error wrapped by this result
*/
E error;
/**
* Changes the success type of this result.
*
* @param target success type
* @return this result with changed success type
*/
@SuppressWarnings("unchecked")
private <@Any R> Result transmuteResultType() {
return (Result) this;
}
//
@Override
public boolean isSuccess() {
return false;
}
@Override
public boolean isError() {
return true;
}
//
//
@Override
public T unwrap() {
throw new NotSuccessException("This is not a success-result");
}
@Override
public T expect(final @NonNull String message) {
throw new NotSuccessException(message);
}
@Override
public T orElseThrow(
final @NonNull Function super E, ? extends X> exceptionFactory
) throws X {
throw exceptionFactory.apply(error);
}
@Override
public T orDefault(final T defaultValue) {
return defaultValue;
}
@Override
public T orGetDefault(final @NonNull Supplier extends T> defaultValueSupplier) {
return defaultValueSupplier.get();
}
@Override
public E unwrapError() {
return error;
}
@Override
public E expectError(final String message) {
return error;
}
@Override
public E errorOrElseThrow(
final @NonNull Function super T, ? extends X> exceptionFactory
) {
return error;
}
@Override
public void ifSuccess(final @NonNull Consumer super T> successConsumer) {}
@Override
public void ifError(final @NonNull Consumer super E> errorConsumer) {
errorConsumer.accept(error);
}
@Override
public void handle(
final @NonNull Consumer super T> successConsumer,
final @NonNull Consumer super E> errorConsumer
) {
errorConsumer.accept(error);
}
//
//
@Override
public <@Any R> @NotNull Result map(final @NonNull Function super T, ? extends R> mappingFunction) {
return transmuteResultType();
}
@Override
public @NotNull Result peek(final @NonNull Consumer super T> consumer) {
return this;
}
@Override
public @NotNull Result mapError(final @NonNull Function super E, ? extends R> mappingFunction) {
return error(mappingFunction.apply(error));
}
@Override
public @NotNull Result peekError(final @NonNull Consumer super E> consumer) {
consumer.accept(error);
return this;
}
@Override
public <@Any R> @NotNull Result and(final @NonNull Result nextResult) {
return transmuteResultType();
}
@Override
public <@Any R> @NotNull Result flatMap(
final @NonNull Function super T, ? extends @NotNull Result> mapper
) {
return transmuteResultType();
}
@Override
public @NotNull Result or(final @NonNull Result alternateResult) {
return alternateResult;
}
@Override
public @NotNull Result orElse(
final @NonNull Function super E, ? extends @NotNull Result> mapper
) {
return mapper.apply(error);
}
@Override
public @NotNull Result swap() {
return success(error);
}
//
//
@Override
public @NotNull Optional asOptional() {
return Optional.empty();
}
@Override
public @NotNull Optional asErrorOptional() {
return Optional.ofNullable(error);
}
@Override
public @NotNull ValueContainer asValueContainer() {
return ValueContainer.empty();
}
@Override
public @NotNull ValueContainer asErrorValueContainer() {
return ValueContainer.of(error);
}
//
}
/**
* Holder of a {@code null}-success result.
*/
@UtilityClass
final class NullSuccess {
/**
* Instance of a {@code null}-error
*/
private final Result<@Nullable ?, ?> INSTANCE = new Success<>(null);
}
/**
* Holder of a {@code null}-error result.
*/
@UtilityClass
final class NullError {
/**
* Instance of a {@code null}-error
*/
private final Result, @Nullable ?> INSTANCE = new Error<>(null);
}
/**
* An exception thrown whenever {@link #unwrap()} is called on an error result.
*/
@NoArgsConstructor
@SuppressWarnings("PublicConstructor")
class NotSuccessException extends RuntimeException {
//
/**
* Constructs a new exception with the specified message.
*
* @param message message describing the exception cause
*/
public NotSuccessException(final String message) {
super(message);
}
/**
* Constructs a new exception with the specified message and cause.
*
* @param message message describing the exception cause
* @param cause cause of this exception
*/
public NotSuccessException(final String message, final Throwable cause) {
super(message, cause);
}
/**
* Constructs a new exception with the specified cause.
*
* @param cause cause of this exception
*/
public NotSuccessException(final Throwable cause) {
super(cause);
}
/**
* Constructs a new exception with the specified message and cause.
*
* @param message message describing the exception cause
* @param cause cause of this exception
* @param enableSuppression flag indicating whether or not suppression
* is enabled or disabled for this exception
* @param writableStackTrace flag indicating whether or not not the stack trace
* should be writable for this exception
*/
public NotSuccessException(final String message, final Throwable cause, final boolean enableSuppression,
final boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
//
}
/**
* An exception thrown whenever {@link #unwrapError()} is called on a successful result.
*/
@NoArgsConstructor
@SuppressWarnings("PublicConstructor")
class NotErrorException extends RuntimeException {
//
/**
* Constructs a new exception with the specified message.
*
* @param message message describing the exception cause
*/
public NotErrorException(final String message) {
super(message);
}
/**
* Constructs a new exception with the specified message and cause.
*
* @param message message describing the exception cause
* @param cause cause of this exception
*/
public NotErrorException(final String message, final Throwable cause) {
super(message, cause);
}
/**
* Constructs a new exception with the specified cause.
*
* @param cause cause of this exception
*/
public NotErrorException(final Throwable cause) {
super(cause);
}
/**
* Constructs a new exception with the specified message and cause.
*
* @param message message describing the exception cause
* @param cause cause of this exception
* @param enableSuppression flag indicating whether or not suppression
* is enabled or disabled for this exception
* @param writableStackTrace flag indicating whether or not not the stack trace
* should be writable for this exception
*/
public NotErrorException(final String message, final Throwable cause, final boolean enableSuppression,
final boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
//
}
/**
* Extensions for Result providing type-specific specializations which are impossible via generic virtual methods.
*/
@UtilityClass
class Extensions {
/**
* Rethrows the exception if it is an {@link #error(Object) error result}.
*
* @param result result to be handled
* @param type of the result
* @param type of the throwable error
* @return successful result's value if it was a {@link #isSuccess() successful result}
* @throws X if it was an {@link #isError() error result}
*/
public T rethrow(
final @NotNull Result extends T, ? extends @NotNull X> result
) throws X {
return result.orElseThrow(Function.identity());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy