Skip to content

Commit

Permalink
Introduce MultipleFailures class and failure is not a list anymore
Browse files Browse the repository at this point in the history
  • Loading branch information
chiquitinxx committed Feb 9, 2023
1 parent 6fbf229 commit 25cb725
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 206 deletions.
42 changes: 18 additions & 24 deletions src/main/java/dev/yila/functional/LazyResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import dev.yila.functional.failure.LazyResultException;
import dev.yila.functional.failure.ThrowableFailure;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Function;
Expand All @@ -26,30 +24,26 @@ public static <V> LazyResult<V> create(Supplier<V> supplier) {
}

public static LazyResult failure(Failure failure) {
return new LazyResult(Collections.singletonList(failure));
}

public static LazyResult failures(List<Failure> failures) {
return new LazyResult(failures);
return new LazyResult(failure);
}

private final Supplier<CompletableFuture<T>> supplier;
private final List<Failure> failures;
private final Failure failure;
private Result<T> result;
private CompletableFuture<Result<T>> completableFuture;

private LazyResult(Supplier<T> supplier) {
this.supplier = () -> CompletableFuture.supplyAsync(supplier);
this.failures = null;
this.failure = null;
}

private LazyResult(Supplier<CompletableFuture<T>> supplier, List<Failure> failures) {
private LazyResult(Supplier<CompletableFuture<T>> supplier, Failure failure) {
this.supplier = supplier;
this.failures = failures;
this.failure = failure;
}

private LazyResult(List<Failure> failures) {
this.failures = failures;
private LazyResult(Failure failure) {
this.failure = failure;
this.supplier = null;
}

Expand All @@ -61,8 +55,8 @@ public <V> LazyResult<V> map(Function<T, V> function) {
if (function == null) {
throw new IllegalArgumentException("null is not a valid function to use LazyResult.map");
}
if (this.failures != null) {
return LazyResult.failures(this.failures);
if (this.failure != null) {
return LazyResult.failure(this.failure);
} else {
return joinWithFunction(function);
}
Expand All @@ -72,8 +66,8 @@ public <V> LazyResult<V> flatMap(Function<T, LazyResult<V>> lazyResultFunction)
if (lazyResultFunction == null) {
throw new IllegalArgumentException("null is not a valid function to use LazyResult.flatMap");
}
if (this.failures != null) {
return LazyResult.failures(this.failures);
if (this.failure != null) {
return LazyResult.failure(this.failure);
} else {
return joinWithLazyResultFunction(lazyResultFunction);
}
Expand All @@ -87,18 +81,18 @@ public synchronized CompletableFuture<Result<T>> start() {
}

private <V> LazyResult<V> joinWithFunction(Function<T, V> function) {
return new LazyResult<>(() -> this.supplier.get().thenApplyAsync(function), this.failures);
return new LazyResult<>(() -> this.supplier.get().thenApplyAsync(function), this.failure);
}

private <V> LazyResult<V> joinWithLazyResultFunction(Function<T, LazyResult<V>> lazyResultFunction) {
return new LazyResult<V>(() -> this.supplier.get().thenApplyAsync(lazyResultFunction)
.thenApplyAsync(this::extract), this.failures);
.thenApplyAsync(this::extract), this.failure);
}

private <V> V extract(LazyResult<V> lazyResult) {
Result<V> result = lazyResult.getResult();
if (result.hasFailures()) {
throw new LazyResultException(result.getFailures());
if (result.hasFailure()) {
throw new LazyResultException(result.failure().get());
} else {
return result.getOrThrow();
}
Expand All @@ -107,8 +101,8 @@ private <V> V extract(LazyResult<V> lazyResult) {
private Result<T> getResult() {
synchronized (this) {
if (this.result == null) {
if (this.failures != null) {
this.result = Result.failures(this.failures);
if (this.failure != null) {
this.result = Result.failure(this.failure);
} else {
this.result = start().join();
}
Expand All @@ -122,7 +116,7 @@ private CompletableFuture<Result<T>> executeSupplier() {
if (throwable != null) {
CompletionException completionException = (CompletionException) throwable;
if (throwable.getCause() instanceof LazyResultException) {
return Result.failures(((LazyResultException) throwable.getCause()).getFailures());
return Result.failure(((LazyResultException) throwable.getCause()).getFailure());
} else {
return Result.failure(new ThrowableFailure(completionException.getCause()));
}
Expand Down
138 changes: 40 additions & 98 deletions src/main/java/dev/yila/functional/Result.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package dev.yila.functional;

import dev.yila.functional.failure.Failure;
import dev.yila.functional.failure.MultipleFailures;
import dev.yila.functional.failure.ThrowableFailure;

import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
* Class to store the result or the failures
* @param <T> Type of result
* Class to store the result value or the failure
* @param <T> Type of result value
*/
public class Result<T> {

Expand All @@ -38,7 +42,7 @@ public static <T> Result<T> failure(Failure failure) {
if (failure == null) {
throw new IllegalArgumentException("Fail can not be null.");
}
return new Result<>(null, Collections.singletonList(failure));
return new Result<>(null, failure);
}

/**
Expand All @@ -51,7 +55,7 @@ public static <T> Result<T> failure(Throwable throwable) {
if (throwable == null) {
throw new IllegalArgumentException("Fail can not be null.");
}
return new Result<>(null, Collections.singletonList(new ThrowableFailure(throwable)));
return new Result<>(null, new ThrowableFailure(throwable));
}

/**
Expand All @@ -64,7 +68,7 @@ public static <T> Result<T> failures(List<Failure> failures) {
if (failures == null || failures.size() < 1) {
throw new IllegalArgumentException("Failures list can not be null.");
}
return new Result<>(null, Collections.unmodifiableList(failures));
return new Result<>(null, new MultipleFailures(failures));
}

/**
Expand Down Expand Up @@ -108,7 +112,7 @@ public static <T> Result<T> flatCreate(Supplier<Result<T>> supplier) {
}

private final T value;
private final List<Failure> failures;
private final Failure failure;

/**
* Join multiple Results in one Result
Expand All @@ -117,7 +121,7 @@ public static <T> Result<T> flatCreate(Supplier<Result<T>> supplier) {
*/
public static Result<List> join(Result... results) {
List okResults = Arrays.stream(results)
.filter(result -> !result.hasFailures())
.filter(result -> !result.hasFailure())
.map(Result::getOrThrow)
.collect(Collectors.toList());
if (okResults.size() == results.length) {
Expand All @@ -131,8 +135,8 @@ public static Result<List> join(Result... results) {
* Check the current result has failures.
* @return boolean
*/
public boolean hasFailures() {
return failures != null;
public boolean hasFailure() {
return failure != null;
}

/**
Expand All @@ -141,8 +145,8 @@ public boolean hasFailures() {
* @return T
*/
public T getOrThrow() {
if (hasFailures()) {
throw new NoSuchElementException("Value not present, there are failures: " + getFailuresToString());
if (hasFailure()) {
throw new NoSuchElementException("Value not present, failure: " + failure.toString());
}
return value;
}
Expand All @@ -153,7 +157,7 @@ public T getOrThrow() {
* @return T
*/
public T orElse(Function<Result<T>, T> function) {
if (this.hasFailures()) {
if (this.hasFailure()) {
return function.apply(this);
}
return value;
Expand All @@ -163,31 +167,8 @@ public T orElse(Function<Result<T>, T> function) {
* Get failures or null if is success
* @return List<Failure>
*/
public List<Failure> getFailures() {
return this.failures;
}

/**
* Get failures as String
* @return String
*/
public String getFailuresToString() {
return showFailures(Failure::toString);
}

/**
* Failure codes as comma separated list.
* @return String
*/
public String getFailuresCode() {
return showFailures(Failure::getCode);
}

private String showFailures(Function<Failure, String> failureToString) {
return "[" + this.failures.stream()
.map(failureToString)
.collect(Collectors.joining(", ")) +
"]";
public Optional<Failure> failure() {
return Optional.ofNullable(this.failure);
}

/**
Expand All @@ -197,7 +178,7 @@ private String showFailures(Function<Failure, String> failureToString) {
* @return Result<R>
*/
public <R> Result<R> flatMap(Function<T, Result<R>> function) {
if (hasFailures()) {
if (hasFailure()) {
return (Result<R>) this;
} else {
return function.apply(this.value);
Expand All @@ -213,7 +194,7 @@ public <R> Result<R> flatMap(Function<T, Result<R>> function) {
* @param <K>
*/
public <R, K extends Throwable> Result<R> flatMap(ThrowingFunctionException<T, R, K> function, Class<K> throwableClass) {
if (hasFailures()) {
if (hasFailure()) {
return (Result<R>) this;
} else {
return createChecked(() -> function.apply(this.value), throwableClass);
Expand All @@ -227,7 +208,7 @@ public <R, K extends Throwable> Result<R> flatMap(ThrowingFunctionException<T, R
* @return Result<R>
*/
public <R> Result<R> map(Function<T, R> function) {
if (hasFailures()) {
if (hasFailure()) {
return (Result<R>) this;
} else {
return Result.ok(function.apply(this.value));
Expand All @@ -240,7 +221,7 @@ public <R> Result<R> map(Function<T, R> function) {
* @return Result<T>
*/
public Result<T> onSuccess(Consumer<T> consumer) {
if (!hasFailures()) {
if (!hasFailure()) {
consumer.accept(this.value);
}
return this;
Expand All @@ -251,78 +232,39 @@ public Result<T> onSuccess(Consumer<T> consumer) {
* @param consumer
* @return Result<T>
*/
public Result<T> onFailures(Consumer<Result<T>> consumer) {
if (hasFailures()) {
public Result<T> onFailure(Consumer<Result<T>> consumer) {
if (hasFailure()) {
consumer.accept(this);
}
return this;
}

/**
* Check if the result not has a failure of the provided class
* @param failureClass
* @return boolean
*/
public boolean notHasFailure(Class<? extends Failure> failureClass) {
return !hasFailures() || failures.stream().noneMatch(failureClass::isInstance);
@Override
public String toString() {
return "Result(" + (this.hasFailure() ? "FAILURE" : "OK") + "): " +
(this.hasFailure() ? this.failure.toString() : value.toString());
}

/**
* Check if the result has a failure of the provided class
* @param failureClass
* @return boolean
* Get value in the result.
* @return
*/
public boolean hasFailure(Class<? extends Failure> failureClass) {
return hasFailures() && failures.stream().anyMatch(failureClass::isInstance);
}

@Override
public String toString() {
return "Result(" + (this.hasFailures() ? "FAILURES" : "OK") + "): " +
(this.hasFailures() ? this.getFailuresToString() : value.toString());
public Optional<T> value() {
if (hasFailure()) {
return Optional.empty();
}
return Optional.of(value);
}

private Result(T value, List<Failure> failures) {
private Result(T value, Failure failure) {
this.value = value;
this.failures = failures;
this.failure = failure;
}

private static List<Failure> joinFailures(List<Result<?>> results) {
return results.stream()
.filter(Result::hasFailures)
.flatMap(result -> result.getFailures().stream())
.filter(Result::hasFailure)
.map(result -> result.failure().get())
.collect(Collectors.toList());
}

/**
* Get failures as a Throwable
* @return Throwable
*/
public Throwable getFailuresAsThrowable() {
if (hasFailures()) {
return this.failures.stream().skip(1)
.map(this::failureToThrowable)
.reduce(failureToThrowable(this.failures.get(0)), (all, current) -> {
all.addSuppressed(current);
return all;
});
} else {
throw new NoSuchElementException("Result has not failures");
}
}

private Throwable failureToThrowable(Failure failure) {
if (failure instanceof ThrowableFailure) {
return ((ThrowableFailure)failure).getThrowable();
} else {
return new Throwable(failure.toString());
}
}

public Optional<T> toOptional() {
if (hasFailures()) {
return Optional.empty();
}
return Optional.of(value);
}
}
8 changes: 8 additions & 0 deletions src/main/java/dev/yila/functional/failure/Failure.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,12 @@ static Failure create(String code, String description) {
default String getCode() {
return this.getClass().getName();
}

default Throwable toThrowable() {
if (this instanceof ThrowableFailure) {
return ((ThrowableFailure)this).getThrowable();
} else {
return new Throwable(this.toString());
}
}
}
Loading

0 comments on commit 25cb725

Please sign in to comment.