All Downloads are FREE. Search and download functionalities are using the official Maven repository.

panda.std.Result Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2021 dzikoysk
 *
 * 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 panda.std;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import panda.std.function.ThrowingConsumer;
import panda.std.function.ThrowingFunction;
import panda.std.function.ThrowingRunnable;
import panda.std.function.ThrowingSupplier;

import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static panda.std.Blank.BLANK;
import static panda.std.Blank.voidness;
import static panda.std.Result.State.OK;
import static panda.std.Result.State.ERROR;

/**
 * {@link panda.std.Result} represents value or associated error that caused the absence of the expected value.
 * By definition, Result has to contain one non-null value - the value or the error.
 * If you want to use nullable value or nullable error, you have to use wrapper like {@link panda.std.Option} to explicitly declare it.
 *
 * @param  type of value
 * @param  type of error
 */
public class Result  {

    public enum State {
        OK,
        ERROR
    }

    private final State state;
    private final VALUE value;
    private final ERROR error;

    private Result(State state, @Nullable VALUE value, @Nullable ERROR error) {
        if (value != null && error != null) {
            throw new IllegalStateException("Value and error are not null - Cannot determine state of Result");
        }

        this.state = state;
        this.value = value;
        this.error = error;
    }

    public static  @NotNull Result ok(VALUE value) {
        return new Result<>(OK, value, null);
    }

    public static  @NotNull Result ok() {
        return new Result<>(OK, BLANK, null);
    }

    public static  @NotNull Result error(ERROR err) {
        return new Result<>(ERROR, null, err);
    }

    public static  @NotNull Result error() {
        return new Result<>(ERROR, null, BLANK);
    }

    public static  @NotNull Result when(boolean condition, @NotNull Supplier value, @NotNull Supplier err) {
        return condition ? ok(value.get()) : error(err.get());
    }

    public static  @NotNull Result when(boolean condition, VALUE value, ERROR err) {
        return condition ? ok(value) : error(err);
    }

    public static  @NotNull Result runThrowing(@NotNull ThrowingRunnable<@NotNull Exception> runnable) throws AttemptFailedException {
        return runThrowing(Exception.class, runnable);
    }

    public static  @NotNull Result runThrowing(
        @NotNull Class exceptionType,
        @NotNull ThrowingRunnable<@NotNull ERROR> runnable
    ) throws AttemptFailedException {
        return supplyThrowing(exceptionType, () -> {
            runnable.run();
            return voidness();
        });
    }

    /**
     * @see panda.std.Result#supplyThrowing(panda.std.function.ThrowingSupplier)
     */
    @Deprecated
    public static  @NotNull Result attempt(@NotNull ThrowingSupplier supplier) {
        return supplyThrowing(Exception.class, supplier);
    }

    /**
     * @see panda.std.Result#supplyThrowing(Class, panda.std.function.ThrowingSupplier)
     */
    @Deprecated
    public static  @NotNull Result attempt(
        @NotNull Class exceptionType,
        @NotNull ThrowingSupplier supplier
    ) throws AttemptFailedException {
        return supplyThrowing(exceptionType, supplier);
    }

    public static  @NotNull Result supplyThrowing(@NotNull ThrowingSupplier supplier) {
        return supplyThrowing(Exception.class, supplier);
    }

    public static  @NotNull Result supplyThrowing(
            @NotNull Class exceptionType,
            @NotNull ThrowingSupplier supplier
    ) throws AttemptFailedException {
        try {
            return Result.ok(supplier.get());
        } catch (Throwable throwable) {
            if (exceptionType.isAssignableFrom(throwable.getClass())) {
                //noinspection unchecked
                return Result.error((ERROR) throwable);
            }

            throw new AttemptFailedException(throwable);
        }
    }

    public  @NotNull Result merge(
        @NotNull Result second,
        @NotNull BiFunction mergeFunction
    ) {
        return flatMap(firstValue -> second.map(secondValue -> mergeFunction.apply(firstValue, secondValue)));
    }

    public  @NotNull Result map(@NotNull Function function) {
        return isOk() ? ok(function.apply(get())) : projectToError();
    }

    public @NotNull Result mapToBlank() {
        return isOk() ? ok() : projectToError();
    }

    public @NotNull Result mapErrToBlank() {
        return isErr() ? error() : projectToValue();
    }

    public  @NotNull Result mapErr(@NotNull Function function) {
        return isOk() ? projectToValue() : error(function.apply(getError()));
    }

    public  @NotNull Result flatMap(@NotNull Function> function) {
        //noinspection unchecked
        return isOk()
                ? (Result) function.apply(get())
                : projectToError();
    }

    public  @NotNull Result flatMapErr(@NotNull Function<@NotNull ERROR, @NotNull Result> function) {
        return isErr()
                ? function.apply(getError())
                : projectToValue();
    }

    public @NotNull Result filter(@NotNull Predicate predicate, @NotNull Function errorSupplier) {
        return isOk() && !predicate.test(get()) ? error(errorSupplier.apply(get())) : this;
    }

    public @NotNull Result filterNot(@NotNull Predicate predicate, @NotNull Function errorSupplier) {
        return filter(value -> !predicate.test(value), errorSupplier);
    }

    public @NotNull Result filter(@NotNull Function filtersBody) {
        return flatMap(value -> {
            ERROR error = filtersBody.apply(value);
            return error == null ? ok(value) : error(error);
        });
    }

    public static class FilteredResult {

        private final @Nullable Result previousError;
        private final @Nullable Result currentResult;

        private FilteredResult(@Nullable Result previousError, @Nullable Result currentResult) {
            if (previousError == null && currentResult == null) {
                throw new IllegalArgumentException("Both previousError and currentResult are null");
            }
            this.previousError = previousError;
            this.currentResult = currentResult;
        }

        public @NotNull Result mapFilterError(@NotNull Function mapper) {
            return previousError != null
                    ? previousError
                    : Objects.requireNonNull(currentResult).mapErr(mapper);
        }

    }

    public @NotNull  FilteredResult filterWithThrowing(@NotNull ThrowingConsumer filtersBody) {
        return fold(
            value -> {
                try {
                    filtersBody.accept(value);
                    return new FilteredResult<>(null, ok(value));
                } catch (Exception e) {
                    //noinspection unchecked
                    return new FilteredResult<>(null, error((E) e));
                }
            },
            error -> new FilteredResult<>(error(error), null)
        );
    }

    public  COMMON fold(@NotNull Function valueMerge, @NotNull Function errorMerge) {
        return isOk() ? valueMerge.apply(get()) : errorMerge.apply(getError());
    }

    public boolean matches(Predicate condition) {
        return isOk() && condition.test(value);
    }

    public  @NotNull Result is(@NotNull Class type, @NotNull Function errorSupplier) {
        return this
                .filter(type::isInstance, errorSupplier)
                .map(type::cast);
    }

    public @NotNull Result swap() {
        return isOk() ? error(get()) : ok(getError());
    }

    public Result consume(@NotNull Consumer valueConsumer, @NotNull Consumer errorConsumer) {
        return this.peek(valueConsumer).onError(errorConsumer);
    }

    @SuppressWarnings("unchecked")
    public  @NotNull Result project() {
        return (Result) this;
    }

    @SuppressWarnings("unchecked")
    public  @NotNull Result projectToValue() {
        return (Result) this;
    }

    @SuppressWarnings("unchecked")
    public  @NotNull Result projectToError() {
        return (Result) this;
    }

    public @NotNull Result orElse(@NotNull Function> orElse) {
        return isOk() ? this : orElse.apply(getError());
    }

    public @NotNull VALUE orElseGet(@NotNull Function orElse) {
        return isOk() ? get() : orElse.apply(getError());
    }

    public  @NotNull VALUE orThrow(@NotNull ThrowingFunction consumer) throws E {
        if (isOk()) {
            return get();
        }

        throw consumer.apply(getError());
    }

    /**
     * @see panda.std.Result#orThrow(panda.std.function.ThrowingFunction)
     */
    @Deprecated
    public  @NotNull VALUE orElseThrow(@NotNull ThrowingFunction consumer) throws E {
        return orThrow(consumer);
    }

    public @NotNull Result peek(@NotNull Consumer consumer) {
        if (isOk()) {
            consumer.accept(get());
        }

        return this;
    }

    public @NotNull Result onError(@NotNull Consumer consumer) {
        if (isErr()) {
            consumer.accept(getError());
        }

        return this;
    }

    public boolean isOk() {
        return state == OK;
    }

    public boolean isErr() {
        return state == ERROR;
    }

    public VALUE get() {
        if (isErr()) {
            throw new IllegalStateException("Result contains error - Cannot get the success value");
        }

        return value;
    }

    public ERROR getError() {
        if (isOk()) {
            throw new IllegalStateException("Result completed successfully - Cannot get the error value");
        }

        return error;
    }

    public Object getAny() {
        return isOk() ? value : error;
    }

    @SuppressWarnings("unchecked")
    public  AS getAnyAs() {
        return (AS) getAny();
    }

    public @NotNull Option<@NotNull VALUE> toOption() {
        return Option.of(value);
    }

    public @NotNull Option<@NotNull ERROR> errorToOption() {
        return Option.of(error);
    }

    public @Nullable VALUE orNull() {
        return value;
    }

    public State getState() {
        return state;
    }

    @Override
    public boolean equals(Object to) {
        if (this == to) {
            return true;
        }

        if (to == null || getClass() != to.getClass()) {
            return false;
        }

        Result other = (Result) to;
        return Objects.equals(value, other.value) && Objects.equals(error, other.error);
    }

    @Override
    public int hashCode() {
        return Objects.hash(value, error);
    }

    @Override
    public String toString() {
        return "Result{" + (isOk() ? "VALUE=" + value : "ERR=" + error) + "}";
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy