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

net.diversionmc.error.Success Maven / Gradle / Ivy

There is a newer version: 1.29.2
Show newest version
package net.diversionmc.error;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;

import static java.util.Collections.reverse;
import static net.diversionmc.error.Functionals.*;

/**
 * Void success result immutable type.
 *
 * @param  Error type.
 * @see Result
 * @see Optional
 */
public final class Success {
    //
    // Factory
    //

    private static final Success OK = new Success<>(null);

    /**
     * Create a success success.
     *
     * @param  Error type.
     * @return Success success. Use {@link Result} if you want an error type.
     */
    @SuppressWarnings("unchecked")
    public static  Success ok() {
        return (Success) OK;
    }

    /**
     * Create an error success.
     *
     * @param e   Error value.
     * @param  Error type.
     * @return Error success.
     */
    public static  Success error(E e) {
        if (e == null) throw new IllegalArgumentException("Supplied null to success");
        return new Success<>(e);
    }

    /**
     * Convert a boolean into Success.
     *
     * @param test  Boolean to convert.
     * @param error Object supplied if false.
     * @param    Error type.
     * @return Ok if true, else error.
     */
    public static  Success check(boolean test, Supplier error) {
        return test ? ok() : error(error.get());
    }

    /**
     * Convert a boolean into Success.
     *
     * @param test  Boolean to convert.
     * @param error Object supplied if false.
     * @param    Error type.
     * @return Ok if true, else error.
     */
    public static  Success flatCheck(boolean test, Supplier> error) {
        return test ? ok() : error.get();
    }

    //
    // Boilerplate
    //

    private final E error;

    private Success(E error) {
        this.error = error;
    }

    /**
     * Get error value.
     *
     * @return Error value if success is unsuccessful or empty.
     */
    public Optional error() {
        return optional(error);
    }

    /**
     * Get stream of the error.
     *
     * @return Error value if success is unsuccessful or empty.
     */
    public Stream errorStream() {
        return stream(error);
    }

    /**
     * Check if success is successful.
     *
     * @return True if success success.
     */
    public boolean isOk() {
        return error == null;
    }

    /**
     * Check if success is unsuccessful.
     *
     * @return True if error success.
     */
    public boolean isError() {
        return error != null;
    }

    /**
     * Debug representation of the success.
     *
     * @return Empty string or error.
     */
    public String toString() {
        return isOk()
            ? ""
            : error + "";
    }

    /**
     * Compare to either {@link Result}, {@link Success} or {@link Optional}.
     *
     * @param obj Object to compare to.
     * @return True if underlying objects under those 3 types match this success (error) or are missing.
     */
    public boolean equals(Object obj) {
        return (obj instanceof Result r2 && isOk() == r2.isOk() && (!isOk() && error.equals(r2.error().get())))
            || (obj instanceof Success s2 && isOk() == s2.isOk() && (isOk() || error.equals(s2.error)))
            || (obj instanceof Optional o2 && isOk() == o2.isPresent() && (!isOk()));
    }

    /**
     * This hashCode method refers to hashCode of error. 0 for successful successes.
     *
     * @return Error hashCode if this success is unsuccessful, 0 otherwise.
     */
    public int hashCode() {
        return isError()
            ? error.hashCode()
            : 0;
    }

    //
    // Function
    //

    /**
     * Convert a success into another success.
     *
     * @param mapper Converter.
     * @param    New error type.
     * @return New success.
     */
    public  Success map(Function, Success> mapper) {
        return mapper.apply(this);
    }

    /**
     * Convert the success value.
     *
     * @param mapper Converter.
     * @param    New success type.
     * @return New result.
     */
    public  Result mapOk(Supplier mapper) {
        return isOk()
            ? Result.ok(mapper.get())
            : Result.error(error);
    }

    /**
     * Convert the error value.
     *
     * @param mapper Converter.
     * @param    New error type.
     * @return New result.
     */
    public  Success mapError(Function mapper) {
        return isOk()
            ? ok()
            : error(mapper.apply(error));
    }

    /**
     * Convert the success to a new result.
     *
     * @param mapper Converter.
     * @param    New success type.
     * @return New result.
     */
    public  Result flatMapResult(Supplier> mapper) {
        return isOk()
            ? mapper.get()
            : Result.error(error);
    }

    /**
     * Convert the success to a new success.
     *
     * @param mapper Converter.
     * @return New success.
     */
    public Success flatMapSuccess(Supplier> mapper) {
        return isOk()
            ? mapper.get()
            : this;
    }

    /**
     * Peek at the success without changing it.
     *
     * @param peeker Peeker function.
     */
    public Success peek(Consumer> peeker) {
        peeker.accept(this);
        return this;
    }

    /**
     * Peek at the success without changing it, if it is present.
     *
     * @param peeker Peeker function.
     */
    public Success peekOk(Runnable peeker) {
        if (isOk()) peeker.run();
        return this;
    }

    /**
     * Peek at the error value without changing it, if it is present.
     *
     * @param peeker Peeker function.
     */
    public Success peekError(Consumer peeker) {
        if (isError()) peeker.accept(error);
        return this;
    }

    /**
     * Ensure both successes are successful.
     *
     * @param other Other to use if this one is ok.
     * @return Combined successes.
     */
    public Success and(Success other) {
        return isOk()
            ? other
            : this;
    }

    /**
     * Ensure either success is successful.
     *
     * @param other Other to check if this one is error.
     * @return Either this or other, but if other success fails, this error is used, ignoring other's error.
     */
    public Success or(Success other) {
        return isOk()
            ? ok()
            : (other.isOk() ? other : this);
    }

    /**
     * Ensure either success is successful or optional is present.
     *
     * @param other Other to check if this one is error.
     * @return Combined successes.
     */
    public Success or(Optional other) {
        return isOk() || other.isPresent()
            ? ok()
            : this;
    }

    //
    // Static
    //

    /**
     * Catch an exception.
     *
     * @param ce  Exception type to catch
     * @param r   TryR to catch
     * @param  Error type
     * @return Ok if successful, error if caught an exception
     */
    @SuppressWarnings("unchecked")
    public static  Success tryRun(Class ce, Runnable r) {
        try {
            r.run();
            return ok();
        } catch (ResultException e) {
            if (ce.isInstance(e.exception()))
                return error((E) e);
            throw e;
        }
    }

    private static List tryCloseAll(List resources) {
        reverse(resources); // reversing a stream does not make sense, so reverse a list
        return resources.stream()
            .map(i -> tryRun(
                Exception.class,
                (TryR) i::close))
            .flatMap(Success::errorStream)
            .toList(); // this lets us close all resources ignoring errors until collect
    }

    @SuppressWarnings("unchecked")
    private static  Success errorCatchRethrow(Class ce, ResultException e) {
        if (ce.isInstance(e.exception()))
            return error((E) e);
        throw e;
    }

    /**
     * Catch an exception.
     *
     * @param ce  Exception type to catch
     * @param r   TryWithResources block to catch
     * @param  Error type
     * @return Ok if successful, error if caught an exception
     */
    public static  Success tryRun(Class ce, TryC r) {
        var resources = new ArrayList();
        try {
            r.accept(new TryWithResources(resources)); // this may throw...
            var closed = tryCloseAll(resources);

            if (closed.isEmpty()) return ok();
            var firstCloseFailure = closed.get(0);
            closed.stream()
                .skip(1)
                .forEach(firstCloseFailure::addSuppressed);
            return errorCatchRethrow(ce, new ResultException(firstCloseFailure));
        } catch (ResultException e) {
            var closed = tryCloseAll(resources); // ...so we need to close all resources before catchRethrow
            var caught = errorCatchRethrow(ce, e);
            closed.forEach(caught.error::addSuppressed);
            return caught;
        }
    }

    /**
     * Catch an exception.
     *
     * @param ce  Exception type to catch
     * @param r   TryWithResources block to catch
     * @param  Error type
     * @return Ok if successful, error if caught an exception
     */
    public static  Success flatTryRun(Class ce, TryF, E> r) {
        var resources = new ArrayList();
        try {
            var res = r.apply(new TryWithResources(resources)); // this may throw...
            var closed = tryCloseAll(resources);

            if (closed.isEmpty()) return res; // this res may be not ok
            var firstCloseFailure = closed.get(0);
            closed.stream()
                .skip(1)
                .forEach(firstCloseFailure::addSuppressed);
            return errorCatchRethrow(ce, new ResultException(firstCloseFailure));
        } catch (ResultException e) {
            var closed = tryCloseAll(resources); // ...so we need to close all resources before catchRethrow
            var caught = errorCatchRethrow(ce, e);
            closed.forEach(caught.error::addSuppressed);
            return caught;
        }
    }

    /**
     * Collect all successes, if possible.
     *
     * @param  Error type
     * @return Result of collected list or first error
     */
    public static  Collector, ?, Success> toSuccess() {
        return Collector.of(
            mapS(Success::ok, AtomicReference::new),
            (successes, success) -> successes.set(successes.get().and(success)),
            (successes, success) -> {
                successes.set(successes.get().and(success.get()));
                return successes;
            },
            AtomicReference::get);
    }

    /**
     * Collect all successes, if possible.
     *
     * @param all Args to collect
     * @param  Error type
     * @return Result of collected args or first error
     */
    @SafeVarargs
    public static  Success all(Success... all) {
        return Arrays.stream(all)
            .collect(toSuccess());
    }

    /**
     * Collect all successes, if possible.
     *
     * @param all Args to collect
     * @param  Error type
     * @return Result of collected args or first error
     */
    @SafeVarargs
    public static  Success flatAll(Supplier>... all) {
        // I don't trust streams collecting with its combiner
        var result = Success.ok();
        for (var res : all)
            result = result.flatMapSuccess(res);
        return result;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy