panda.std.Result Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2021 dzikoysk
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package panda.std;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import panda.std.function.ThrowingConsumer;
import panda.std.function.ThrowingFunction;
import panda.std.function.ThrowingRunnable;
import panda.std.function.ThrowingSupplier;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import static panda.std.Blank.BLANK;
import static panda.std.Blank.voidness;
import static panda.std.Result.State.OK;
import static panda.std.Result.State.ERROR;
/**
* {@link panda.std.Result} represents value or associated error that caused the absence of the expected value.
* By definition, Result has to contain one non-null value - the value or the error.
* If you want to use nullable value or nullable error, you have to use wrapper like {@link panda.std.Option} to explicitly declare it.
*
* @param type of value
* @param type of error
*/
public class Result {
public enum State {
OK,
ERROR
}
private final State state;
private final VALUE value;
private final ERROR error;
private Result(State state, @Nullable VALUE value, @Nullable ERROR error) {
if (value != null && error != null) {
throw new IllegalStateException("Value and error are not null - Cannot determine state of Result");
}
this.state = state;
this.value = value;
this.error = error;
}
public static @NotNull Result ok(VALUE value) {
return new Result<>(OK, value, null);
}
public static @NotNull Result ok() {
return new Result<>(OK, BLANK, null);
}
public static @NotNull Result error(ERROR err) {
return new Result<>(ERROR, null, err);
}
public static @NotNull Result error() {
return new Result<>(ERROR, null, BLANK);
}
public static @NotNull Result when(boolean condition, @NotNull Supplier value, @NotNull Supplier err) {
return condition ? ok(value.get()) : error(err.get());
}
public static @NotNull Result when(boolean condition, VALUE value, ERROR err) {
return condition ? ok(value) : error(err);
}
public static @NotNull Result runThrowing(@NotNull ThrowingRunnable<@NotNull Exception> runnable) throws AttemptFailedException {
return runThrowing(Exception.class, runnable);
}
public static @NotNull Result runThrowing(
@NotNull Class extends ERROR> exceptionType,
@NotNull ThrowingRunnable<@NotNull ERROR> runnable
) throws AttemptFailedException {
return supplyThrowing(exceptionType, () -> {
runnable.run();
return voidness();
});
}
/**
* @see panda.std.Result#supplyThrowing(panda.std.function.ThrowingSupplier)
*/
@Deprecated
public static @NotNull Result attempt(@NotNull ThrowingSupplier supplier) {
return supplyThrowing(Exception.class, supplier);
}
/**
* @see panda.std.Result#supplyThrowing(Class, panda.std.function.ThrowingSupplier)
*/
@Deprecated
public static @NotNull Result attempt(
@NotNull Class extends ERROR> exceptionType,
@NotNull ThrowingSupplier supplier
) throws AttemptFailedException {
return supplyThrowing(exceptionType, supplier);
}
public static @NotNull Result supplyThrowing(@NotNull ThrowingSupplier supplier) {
return supplyThrowing(Exception.class, supplier);
}
public static @NotNull Result supplyThrowing(
@NotNull Class extends ERROR> exceptionType,
@NotNull ThrowingSupplier supplier
) throws AttemptFailedException {
try {
return Result.ok(supplier.get());
} catch (Throwable throwable) {
if (exceptionType.isAssignableFrom(throwable.getClass())) {
//noinspection unchecked
return Result.error((ERROR) throwable);
}
throw new AttemptFailedException(throwable);
}
}
public @NotNull Result merge(
@NotNull Result second,
@NotNull BiFunction mergeFunction
) {
return flatMap(firstValue -> second.map(secondValue -> mergeFunction.apply(firstValue, secondValue)));
}
public @NotNull Result map(@NotNull Function function) {
return isOk() ? ok(function.apply(get())) : projectToError();
}
public @NotNull Result mapToBlank() {
return isOk() ? ok() : projectToError();
}
public @NotNull Result mapErrToBlank() {
return isErr() ? error() : projectToValue();
}
public @NotNull Result mapErr(@NotNull Function function) {
return isOk() ? projectToValue() : error(function.apply(getError()));
}
public @NotNull Result flatMap(@NotNull Function> function) {
//noinspection unchecked
return isOk()
? (Result) function.apply(get())
: projectToError();
}
public @NotNull Result flatMapErr(@NotNull Function<@NotNull ERROR, @NotNull Result> function) {
return isErr()
? function.apply(getError())
: projectToValue();
}
public @NotNull Result filter(@NotNull Predicate predicate, @NotNull Function errorSupplier) {
return isOk() && !predicate.test(get()) ? error(errorSupplier.apply(get())) : this;
}
public @NotNull Result filterNot(@NotNull Predicate predicate, @NotNull Function errorSupplier) {
return filter(value -> !predicate.test(value), errorSupplier);
}
public @NotNull Result filter(@NotNull Function filtersBody) {
return flatMap(value -> {
ERROR error = filtersBody.apply(value);
return error == null ? ok(value) : error(error);
});
}
public static class FilteredResult {
private final @Nullable Result previousError;
private final @Nullable Result currentResult;
private FilteredResult(@Nullable Result previousError, @Nullable Result currentResult) {
if (previousError == null && currentResult == null) {
throw new IllegalArgumentException("Both previousError and currentResult are null");
}
this.previousError = previousError;
this.currentResult = currentResult;
}
public @NotNull Result mapFilterError(@NotNull Function mapper) {
return previousError != null
? previousError
: Objects.requireNonNull(currentResult).mapErr(mapper);
}
}
public @NotNull FilteredResult filterWithThrowing(@NotNull ThrowingConsumer filtersBody) {
return fold(
value -> {
try {
filtersBody.accept(value);
return new FilteredResult<>(null, ok(value));
} catch (Exception e) {
//noinspection unchecked
return new FilteredResult<>(null, error((E) e));
}
},
error -> new FilteredResult<>(error(error), null)
);
}
public COMMON fold(@NotNull Function valueMerge, @NotNull Function errorMerge) {
return isOk() ? valueMerge.apply(get()) : errorMerge.apply(getError());
}
public boolean matches(Predicate condition) {
return isOk() && condition.test(value);
}
public @NotNull Result is(@NotNull Class type, @NotNull Function errorSupplier) {
return this
.filter(type::isInstance, errorSupplier)
.map(type::cast);
}
public @NotNull Result swap() {
return isOk() ? error(get()) : ok(getError());
}
public Result consume(@NotNull Consumer valueConsumer, @NotNull Consumer errorConsumer) {
return this.peek(valueConsumer).onError(errorConsumer);
}
@SuppressWarnings("unchecked")
public @NotNull Result project() {
return (Result) this;
}
@SuppressWarnings("unchecked")
public @NotNull Result projectToValue() {
return (Result) this;
}
@SuppressWarnings("unchecked")
public @NotNull Result projectToError() {
return (Result) this;
}
public @NotNull Result orElse(@NotNull Function> orElse) {
return isOk() ? this : orElse.apply(getError());
}
public @NotNull VALUE orElseGet(@NotNull Function orElse) {
return isOk() ? get() : orElse.apply(getError());
}
public @NotNull VALUE orThrow(@NotNull ThrowingFunction consumer) throws E {
if (isOk()) {
return get();
}
throw consumer.apply(getError());
}
/**
* @see panda.std.Result#orThrow(panda.std.function.ThrowingFunction)
*/
@Deprecated
public @NotNull VALUE orElseThrow(@NotNull ThrowingFunction consumer) throws E {
return orThrow(consumer);
}
public @NotNull Result peek(@NotNull Consumer consumer) {
if (isOk()) {
consumer.accept(get());
}
return this;
}
public @NotNull Result onError(@NotNull Consumer consumer) {
if (isErr()) {
consumer.accept(getError());
}
return this;
}
public boolean isOk() {
return state == OK;
}
public boolean isErr() {
return state == ERROR;
}
public VALUE get() {
if (isErr()) {
throw new IllegalStateException("Result contains error - Cannot get the success value");
}
return value;
}
public ERROR getError() {
if (isOk()) {
throw new IllegalStateException("Result completed successfully - Cannot get the error value");
}
return error;
}
public Object getAny() {
return isOk() ? value : error;
}
@SuppressWarnings("unchecked")
public AS getAnyAs() {
return (AS) getAny();
}
public @NotNull Option<@NotNull VALUE> toOption() {
return Option.of(value);
}
public @NotNull Option<@NotNull ERROR> errorToOption() {
return Option.of(error);
}
public @Nullable VALUE orNull() {
return value;
}
public State getState() {
return state;
}
@Override
public boolean equals(Object to) {
if (this == to) {
return true;
}
if (to == null || getClass() != to.getClass()) {
return false;
}
Result, ?> other = (Result, ?>) to;
return Objects.equals(value, other.value) && Objects.equals(error, other.error);
}
@Override
public int hashCode() {
return Objects.hash(value, error);
}
@Override
public String toString() {
return "Result{" + (isOk() ? "VALUE=" + value : "ERR=" + error) + "}";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy