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

net.adamcin.oakpal.core.Result Maven / Gradle / Ivy

There is a newer version: 2.3.0
Show newest version
package net.adamcin.oakpal.core;


import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;

/**
 * A type representing either a successful result value, or failure, with an error.
 *
 * @param  The result type.
 */
public abstract class Result implements Serializable {
    private static final Logger LOGGER = LoggerFactory.getLogger(Result.class);

    private Result() {
        /* construct from factory */
    }

    /**
     * Map it.
     *
     * @param f   the mapping function
     * @param  the result type.
     * @return a mapped result
     */
    public abstract  @NotNull Result map(final @NotNull Function f);

    /**
     * Flat-map it.
     *
     * @param f   the mapping function
     * @param  the result type
     * @return the flat mapped result
     */
    public abstract  @NotNull Result flatMap(final @NotNull Function> f);

    public abstract V getOrDefault(final V defaultValue);

    public abstract V getOrElse(final @NotNull Supplier defaultValue);

    public abstract Result orElse(final @NotNull Supplier> defaultValue);

    public abstract Stream stream();

    public abstract Result teeLogError();

    public final boolean isSuccess() {
        return !isFailure();
    }

    public final boolean isFailure() {
        return getError().isPresent();
    }

    @SuppressWarnings("WeakerAccess")
    public final Optional toOptional() {
        return stream().findFirst();
    }

   /**
     * All Failures will be created with a top-level RuntimeException. This method returns it if this result is a
     * failure. Otherwise, Optional.empty() is returned for a success.
     *
     * @return the top level runtime exception or empty if success
     */
    public abstract Optional getError();

    /**
     * Filters the exception stack as a stream using the provided Throwable predicate.
     *
     * @param predicate the Throwable filter
     * @return some matching throwable or empty
     */
    @SuppressWarnings("WeakerAccess")
    public final Optional findCause(final @NotNull Predicate predicate) {
        return getError().map(Result::causing).orElse(Stream.empty()).filter(predicate).findFirst();
    }

    /**
     * Filters the exception stack as a stream, matching causes against the provided error type. Since the top-level
     * exception may be an internal RuntimeException, you can use this method to determine if a particular Throwable
     * type was thrown.
     *
     * @param errorType the class providing teh particular Exception type parameter
     * @param  the particular Throwable type parameter
     * @return an Optional error
     */
    @SuppressWarnings("WeakerAccess")
    public final  Optional findCause(final @NotNull Class errorType) {
        return findCause(errorType::isInstance).map(errorType::cast);
    }

    /**
     * Feeling down because of too much functional wrapping? This method has you covered. Rethrow the highest result
     * error cause matching the provided type to so you can catch it like old times.
     *
     * @param errorType the class providing the particular Exception type parameter
     * @param        the particular Exception type parameter
     * @throws E if any cause in the chain is an instance of the provided errorType, that cause is rethrown
     */
    public final  void throwCause(final @NotNull Class errorType) throws E {
        Optional cause = findCause(errorType);
        if (cause.isPresent()) {
            throw cause.get();
        }
    }

    /**
     * Produces a stream of Throwable causes for the provided throwable.
     *
     * @param caused the top-level exception
     * @return a stream of throwable causes
     */
    @SuppressWarnings("WeakerAccess")
    static Stream causing(final @NotNull Throwable caused) {
        return Stream.concat(Optional.of(caused).map(Stream::of).orElse(Stream.empty()),
                Optional.ofNullable(caused.getCause()).map(Result::causing).orElse(Stream.empty()));
    }

    /**
     * Standard forEach method calling a consumer to accept the value. Not executed on a Failure.
     *
     * @param consumer the consumer
     */
    public abstract void forEach(final @NotNull Consumer consumer);

    private static final class Failure extends Result {
        private final RuntimeException exception;

        private Failure(final String message) {
            super();
            this.exception = new IllegalStateException(message);
        }

        private Failure(final @NotNull RuntimeException e) {
            this.exception = e;
        }

        private Failure(final @NotNull Exception e) {
            this.exception = new IllegalStateException(e.getMessage(), e);
        }

        private Failure(final @NotNull String message, final @NotNull Exception e) {
            this.exception = new IllegalStateException(message, e);
        }

        @Override
        public @NotNull  Result map(final @NotNull Function f) {
            return new Failure<>(this.exception);
        }

        @Override
        public @NotNull  Result flatMap(final @NotNull Function> f) {
            return new Failure<>(this.exception);
        }

        @Override
        public V getOrDefault(final V defaultValue) {
            logSupression();
            return defaultValue;
        }

        @Override
        public V getOrElse(final @NotNull Supplier defaultValue) {
            logSupression();
            return defaultValue.get();
        }

        @Override
        public Result orElse(final @NotNull Supplier> defaultValue) {
            logSupression();
            return defaultValue.get();
        }

        @Override
        public void forEach(final @NotNull Consumer consumer) {
            logSupression();
        }

        @Override
        public Stream stream() {
            logSupression();
            return Stream.empty();
        }


        @Override
        public Optional getError() {
            return Optional.of(this.exception);
        }


        @Override
        public String toString() {
            return String.format("Failure(%s)", exception.getMessage());
        }

        @Override
        public Result teeLogError() {
            LOGGER.debug("failure [stacktrace visible in TRACE logging]: {}", this);
            logTrace();
            return this;
        }

        private void logTrace() {
            LOGGER.trace("thrown:", this.exception);
        }

        private void logSupression() {
            LOGGER.debug("failure (suppressed) [stacktrace visible in TRACE logging]: {}", this);
            logTrace();
        }
    }

    private static final class Success extends Result {
        private final V value;

        private Success(final V value) {
            super();
            this.value = value;
        }

        @Override
        public @NotNull  Result map(final @NotNull Function f) {
            return new Success<>(f.apply(value));
        }

        @Override
        public @NotNull  Result flatMap(final @NotNull Function> f) {
            return f.apply(value);
        }

        @Override
        public V getOrDefault(final V defaultValue) {
            return value;
        }

        @Override
        public V getOrElse(final @NotNull Supplier defaultValue) {
            return value;
        }

        @Override
        public Result orElse(final @NotNull Supplier> defaultValue) {
            return this;
        }

        @Override
        public void forEach(final @NotNull Consumer consumer) {
            consumer.accept(value);
        }

        @Override
        public Stream stream() {
            return value != null ? Stream.of(value) : Stream.empty();
        }

        @Override
        public Result teeLogError() {
            return this;
        }

        @Override
        public Optional getError() {
            return Optional.empty();
        }

        @Override
        public String toString() {
            return String.format("Success(%s)", String.valueOf(value));
        }
    }

    public static  Result failure(final String message) {
        return new Failure<>(message);
    }

    public static  Result failure(final @NotNull Exception e) {
        return new Failure<>(e);
    }

    public static  Result failure(final @NotNull String message, final @NotNull Exception e) {
        return new Failure<>(message, e);
    }

    public static  Result failure(final @NotNull RuntimeException e) {
        return new Failure<>(e);
    }

    public static  Result success(final V value) {
        return new Success<>(value);
    }

    /**
     * Builds a result for a wrapped collector.
     *
     * @param  wrapped incremental value type
     * @param  wrapped accumulator type
     */
    public static final class Builder implements Consumer> {
        final Result resultAcc;
        final AtomicReference> latch;
        final BiConsumer accumulator;

        Builder(final @NotNull Result initial,
                final @NotNull BiConsumer accumulator) {
            this.resultAcc = initial;
            this.latch = new AtomicReference<>(resultAcc);
            this.accumulator = accumulator;
        }

        @Override
        public void accept(final Result valueResult) {
            latch.accumulateAndGet(resultAcc,
                    (fromLatch, fromArg) ->
                            fromLatch.flatMap(state ->
                                    valueResult.map(value -> {
                                        accumulator.accept(state, value);
                                        return state;
                                    })));
        }

        Result build() {
            return latch.get();
        }
    }

    /**
     * Create a collector that accumulates a stream of Results into a single Result containing either:
     * 1. the collected values of the streamed results according using supplied collector
     * 2. the first encountered failure
     * 

* This method is indented to invert the relationship between the Result monoid and the Stream/Collector type, * such that this transformation becomes easier: {@code List> -> Result>} * * @param collector the underlying collector * @param the incremental value * @param the intended container type * @param the collector's accumulator type * @return if all successful, a Result of a Collection; otherwise, the first encountered failure */ public static Collector, Builder, Result> tryCollect(final @NotNull Collector collector) { final Supplier> // first arg supplier = () -> new Builder<>(Result.success(collector.supplier().get()), collector.accumulator()); final BiConsumer, Result> // second arg accumulator = Builder::accept; final BinaryOperator> // third arg combiner = (builder0, builder1) -> new Builder<>( builder0.build().flatMap(left -> builder1.build().map(right -> collector.combiner().apply(left, right))), collector.accumulator()); final Function, Result> // fourth arg finisher = acc -> acc.build().map(collector.finisher()); final Collector.Characteristics[] // fifth arg characteristics = collector.characteristics().stream() // remove IDENTITY_FINISH and CONCURRENT, but pass thru the other characteristics. // TODO implement safety for concurrency ? .filter(Fun.inSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH, Collector.Characteristics.CONCURRENT)).negate()) .toArray(Collector.Characteristics[]::new); return Collector.of(supplier, accumulator, combiner, finisher, characteristics); } public static Collector, Stream.Builder>, Stream>> logAndRestream() { return new RestreamLogCollector<>(LOGGER, ""); } public static Collector, Stream.Builder>, Stream>> logAndRestream(final @NotNull String message) { return new RestreamLogCollector<>(LOGGER, ": " + message); } static final class RestreamLogCollector implements Collector, Stream.Builder>, Stream>> { final Supplier>> supplier; final BiConsumer>, Result> accum; RestreamLogCollector(final @NotNull Logger logger, final @NotNull String collectorMessage) { if (logger.isDebugEnabled()) { final Throwable creation = new Throwable(); this.supplier = () -> { logger.debug("result collector (see TRACE for creation stack)" + collectorMessage); logger.trace("created here", creation); return Stream.builder(); }; this.accum = (builder, element) -> builder.accept(element.teeLogError()); } else { this.supplier = Stream::builder; this.accum = Stream.Builder::accept; } } @Override public Supplier>> supplier() { return supplier; } @Override public BiConsumer>, Result> accumulator() { return accum; } @Override public BinaryOperator>> combiner() { return (left, right) -> { right.build().forEachOrdered(left); return left; }; } @Override public Function>, Stream>> finisher() { return Stream.Builder::build; } @Override public Set characteristics() { return Collections.emptySet(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy