net.diversionmc.error.Success 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.*;
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
*/
public static Success tryRun(Class ce, Runnable r) {
try {
r.run();
return ok();
} 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 Success 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 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;
}
@SuppressWarnings("NewMethodNamingConvention")
public void Try(Function orReturn) {
Functionals.Try(this, orReturn);
}
@SuppressWarnings("NewMethodNamingConvention")
public void Try(Supplier orReturn) {
Functionals.Try(this, orReturn);
}
@SuppressWarnings("NewMethodNamingConvention")
public void Try(T orReturn) {
Functionals.Try(this, orReturn);
}
@SuppressWarnings("NewMethodNamingConvention")
public void Try() {
Functionals.Try(this);
}
public void get() {
if (isError()) throw new NoSuchElementException("Cannot unwrap error");
}
}