net.diversionmc.error.Result Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of result Show documentation
Show all versions of result Show documentation
Diversion Network Result type.
package net.diversionmc.error;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
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;
import static java.util.Collections.reverse;
import static net.diversionmc.error.Functionals.*;
import static net.diversionmc.error.Success.tryRun;
/**
* Result immutable type: Success or Error.
*
* @param Success type.
* @param Error type.
* @see Success
* @see Optional
*/
public final class Result {
//
// Factory
//
/**
* Create a success result.
*
* @param s Success value.
* @param Success type.
* @param Error type.
* @return Success result. Use {@link Optional} if you do not want error type.
*/
public static Result ok(S s) {
if (s == null) throw new IllegalArgumentException("Supplied null to result");
return new Result<>(s, null);
}
/**
* Create an error result.
*
* @param e Error value.
* @param Success type.
* @param Error type.
* @return Error result. Use {@link Success} if you do not want success type.
*/
public static Result error(E e) {
if (e == null) throw new IllegalArgumentException("Supplied null to result");
return new Result<>(null, e);
}
/**
* Convert a boolean into Result.
*
* @param test Boolean to convert.
* @param ok Object supplied if true.
* @param error Object supplied if false.
* @param Success type.
* @param Error type.
* @return Ok if true, else error.
*/
public static Result check(boolean test, Supplier ok, Supplier error) {
return test ? ok(ok.get()) : error(error.get());
}
/**
* Convert a boolean into Result.
*
* @param test Boolean to convert.
* @param ifTrue Result supplied if true.
* @param ifFalse Result supplied if false.
* @param Success type.
* @param Error type.
* @return Obvious.
*/
public static Result flatCheck(boolean test, Supplier> ifTrue, Supplier> ifFalse) {
return (test ? ifTrue : ifFalse).get();
}
/**
* Convert an optional into Result.
*
* @param ok Optional to check and turn into success result.
* @param error Object supplied if optional is empty.
* @param Success type.
* @param Error type.
* @return Ok with the optional's value if present, else error.
*/
public static Result from(Optional ok, Supplier error) {
return ok
.map(Result::ok)
.orElseGet(applyFS(Result::error, error));
}
/**
* Convert an optional into Result.
*
* @param check Optional to check and turn into success result.
* @param other Object supplied if optional is empty.
* @param Success type.
* @param Error type.
* @return Ok with the optional's value if present, else other.
*/
public static Result flatFrom(Optional check, Supplier> other) {
return check
.map(Result::ok)
.orElseGet(other);
}
//
// Boilerplate
//
private final S ok;
private final E error;
private Result(S ok, E error) {
this.ok = ok;
this.error = error;
}
/**
* Get success value.
*
* @return Success value if result is successful or empty.
*/
public Optional ok() {
return optional(ok);
}
/**
* Get success value.
*
* @return Success value if result is successful or empty.
*/
public Stream okStream() {
return stream(ok);
}
/**
* Get error value.
*
* @return Error value if result is unsuccessful or empty.
*/
public Optional error() {
return optional(error);
}
/**
* Get error value.
*
* @return Error value if result is unsuccessful or empty.
*/
public Stream errorStream() {
return stream(error);
}
/**
* Check if result is successful.
*
* @return True if success result.
*/
public boolean isOk() {
return ok != null;
}
/**
* Check if result is unsuccessful.
*
* @return True if error result.
*/
public boolean isError() {
return error != null;
}
/**
* Convert result to success (throw out success value).
*
* @return Success object.
*/
public Success success() {
return isOk()
? Success.ok()
: Success.error(error);
}
/**
* Debug representation of the result.
*
* @return Ok or error, converted to string.
*/
public String toString() {
return (isOk() ? ok : 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 result (ok or error) or are missing.
*/
public boolean equals(Object obj) {
return (obj instanceof Result, ?> r2 && isOk() == r2.isOk() && (isOk() ? ok.equals(r2.ok) : error.equals(r2.error)))
|| (obj instanceof Success> s2 && isOk() == s2.isOk() && (isOk() || error.equals(s2.error().get())))
|| (obj instanceof Optional> o2 && isOk() == o2.isPresent() && (!isOk() || ok.equals(o2.get())));
}
/**
* This hashCode method refers to either hashCode of ok or error.
*
* @return Ok hashCode if this result is successful, error hashCode otherwise.
*/
public int hashCode() {
return (isOk() ? ok : error).hashCode();
}
//
// Function
//
/**
* Convert a result into another result.
*
* @param mapper Converter.
* @param New success type.
* @param New error type.
* @return New result.
*/
public Result map(Function, Result> mapper) {
return mapper.apply(this);
}
/**
* Convert the success value.
*
* @param sMapper Success converter.
* @param eMapper Error converter.
* @param New success type.
* @param New error type.
* @return New result.
*/
public Result map(Function sMapper, Function eMapper) {
return isOk()
? ok(sMapper.apply(ok))
: error(eMapper.apply(error));
}
/**
* Convert the success value.
*
* @param mapper Converter.
* @param New success type.
* @return New result.
*/
public Result mapOk(Function mapper) {
return isOk()
? ok(mapper.apply(ok))
: error(error);
}
/**
* Convert the error value.
*
* @param mapper Converter.
* @param New error type.
* @return New result.
*/
public Result mapError(Function mapper) {
return isOk()
? ok(ok)
: error(mapper.apply(error));
}
/**
* Convert the success value to a new result.
*
* @param mapper Converter.
* @param New success type.
* @return New result.
*/
public Result flatMapResult(Function> mapper) {
return isOk()
? mapper.apply(ok)
: error(error);
}
/**
* Convert the success value to a new success.
*
* @param mapper Converter.
* @return New success.
*/
public Success flatMapSuccess(Function> mapper) {
return isOk()
? mapper.apply(ok)
: Success.error(error);
}
/**
* Peek at the result without changing it.
*
* @param peeker Peeker function.
*/
public Result peek(Consumer> peeker) {
peeker.accept(this);
return this;
}
/**
* Peek at the success value without changing it, if it is present.
*
* @param peeker Peeker function.
*/
public Result peekOk(Consumer peeker) {
if (isOk()) peeker.accept(ok);
return this;
}
/**
* Peek at the error value without changing it, if it is present.
*
* @param peeker Peeker function.
*/
public Result peekError(Consumer peeker) {
if (isError()) peeker.accept(error);
return this;
}
/**
* Filter the ok value, do an error if not passed.
*
* @param filter Filter function.
* @param error Error supplier in case ok does not pass filter.
*/
public Result filter(Predicate filter, Supplier error) {
// don't do anything if error or filter passed
// error if ok but filter not passed
return isError()
|| filter.test(ok)
? this
: error(error.get());
}
/**
* Try to receive value from another result.
*
* @param other Other result to use if this result is unsuccessful.
* @return Either this or other, but if other result fails, this error is used, ignoring other's error.
*/
public Result or(Result other) {
return isOk()
? this
: other
.mapError(ignoreS(applyS(error)));
}
/**
* Try to receive value from an optional.
*
* @param other Optional to use if this result is unsuccessful.
* @return Either this or other, but if other result is empty, this error is used, ignoring other's error.
*/
public Result or(Optional other) {
return isOk()
? this
: from(other, applyS(error));
}
/**
* Try to receive value from another result.
*
* @param other Other result to use if this result is unsuccessful.
* @return Either this or other, but if other result fails, this error is used, ignoring other's error.
*/
public Result flatOr(Supplier> other) {
return isOk()
? this
: other.get()
.mapError(ignoreS(applyS(error)));
}
//
// Static Utils
//
/**
* Catch an exception.
*
* @param ce Exception type to catch
* @param s TryS to catch
* @param Success type
* @param Error type
* @return Ok if successful, error if caught an exception
*/
public static Result tryGet(Class ce, Supplier s) {
try {
return ok(s.get());
} catch (ResultException e) {
return errorCatchRethrow(ce, 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 Result errorCatchRethrow(Class ce, ResultException e) {
var ex = e.exception();
if (ce.isInstance(ex))
return error((E) ex);
throw e;
}
/**
* Catch an exception.
*
* @param ce Exception type to catch
* @param f TryWithResources block to catch
* @param Success type
* @param Error type
* @return Ok if successful, error if caught an exception
*/
public static Result tryGet(Class ce, TryF f) {
var resources = new ArrayList();
try {
var res = f.apply(new TryWithResources(resources)); // this may throw...
var closed = tryCloseAll(resources);
if (closed.isEmpty()) return ok(res);
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 = Result.errorCatchRethrow(ce, e);
closed.forEach(caught.error::addSuppressed);
return caught;
}
}
/**
* Catch an exception.
*
* @param ce Exception type to catch
* @param f TryWithResources block to catch
* @param Success type
* @param Error type
* @return Ok if successful, error if caught an exception
*/
public static Result flatTryGet(Class ce, TryF, E> f) {
var resources = new ArrayList();
try {
var res = f.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 = Result.errorCatchRethrow(ce, e);
closed.forEach(caught.error::addSuppressed);
return caught;
}
}
/**
* Collect all successful type to a list, if possible.
*
* @param Success type
* @param Error type
* @return Result of collected list or first error
*/
public static Collector, ?, Result, E>> toResultList() {
return Collector.of(
mapS(mapS(ArrayList::new, Result::, E>ok), AtomicReference::new),
(results, result) -> results.set(result
.flatMapResult(r -> results.get()
.peekOk(list -> list.add(r)))),
(results, result) -> {
results.set(result.get()
.flatMapResult(r -> results.get()
.peekOk(list -> list.addAll(r))));
return results;
},
AtomicReference::get);
}
@SuppressWarnings("NewMethodNamingConvention")
public S Try(Function orReturn) {
return Functionals.Try(this, orReturn);
}
@SuppressWarnings("NewMethodNamingConvention")
public S Try(Supplier orReturn) {
return Functionals.Try(this, orReturn);
}
@SuppressWarnings("NewMethodNamingConvention")
public S Try(T orReturn) {
return Functionals.Try(this, orReturn);
}
@SuppressWarnings("NewMethodNamingConvention")
public S Try() {
return Functionals.Try(this);
}
public S get() {
if (isError()) throw new NoSuchElementException("Cannot unwrap error");
return ok;
}
}