net.adamcin.oakpal.core.Result Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of oakpal-core Show documentation
Show all versions of oakpal-core Show documentation
OakPAL Core Implementation
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 super V, W> 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 super V, Result> f);
public abstract V getOrDefault(final V defaultValue);
public abstract V getOrElse(final @NotNull Supplier extends V> 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 super Throwable> 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 super V> 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 super V, W> f) {
return new Failure<>(this.exception);
}
@Override
public @NotNull Result flatMap(final @NotNull Function super V, Result> f) {
return new Failure<>(this.exception);
}
@Override
public V getOrDefault(final V defaultValue) {
logSupression();
return defaultValue;
}
@Override
public V getOrElse(final @NotNull Supplier extends V> defaultValue) {
logSupression();
return defaultValue.get();
}
@Override
public Result orElse(final @NotNull Supplier> defaultValue) {
logSupression();
return defaultValue.get();
}
@Override
public void forEach(final @NotNull Consumer super V> 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 super V, W> f) {
return new Success<>(f.apply(value));
}
@Override
public @NotNull Result flatMap(final @NotNull Function super V, Result> f) {
return f.apply(value);
}
@Override
public V getOrDefault(final V defaultValue) {
return value;
}
@Override
public V getOrElse(final @NotNull Supplier extends V> defaultValue) {
return value;
}
@Override
public Result orElse(final @NotNull Supplier> defaultValue) {
return this;
}
@Override
public void forEach(final @NotNull Consumer super V> 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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy