All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.xaan.random.result.Result Maven / Gradle / Ivy

There is a newer version: 1.1.1
Show newest version
/*
 * Random Utilities - A bunch of random utilities I figured might be helpful.
 * Copyright © 2020 Jacob Frazier ([email protected])
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see .
 */
package it.xaan.random.result;

import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.annotation.Nullable;

/**
 * Represents a Result. This can be thought of as a more expansive {@link java.util.Optional}. This
 * can be in one of three states: 
* - Success
* - Error
* - Empty
*
* These states can be interacted with the methods {@link #onSuccess(Consumer)}, {@link * #onError(Class, Consumer)}, and {@link #onEmpty(Runnable)} respectively. * * @since 1.0.0 */ @SuppressWarnings({"unused", "WeakerAccess"}) public final class Result { private static final Result EMPTY = Result.from(null, null); private final T element; private final Object error; private Result(@Nullable final T element, @Nullable final Object error) { this.element = element; this.error = error; } // Creation private static Result from(@Nullable U element, @Nullable Object error) { if (element != null && error != null) { // This should literally never happen. Unless the user is making some sort of reflection // hacks. throw new IllegalStateException( "Both element and error can't be non-null. At least one must be null. This shouldn't be possible. Please make an issue on the github."); } return new Result<>(element, error); } /** * Creates a new {@link Result} in a Success state with the specified non-null element. If your * element is possibly-null see {@link #ofNullable(Object)} * * @param element The element for this Result. * @param The type of the result. * @return An instance of Result in the Success state. * @since 1.0.0 */ public static Result of(U element) { return from(Objects.requireNonNull(element), null); } /** * Creates a new {@link Result} in the Success state or the empty state. * * @param element The possibly-null element for this Result. * @param The type of the result. * @return An instance of Result in the Success state if the element is non-null, otherwise it'll * be in the Empty state. * @since 1.0.0 */ public static Result ofNullable(@Nullable U element) { return element == null ? empty() : of(element); } /** * Creates a new {@link Result} in the Error state. * * @param error The error for this Result. * @param The type of the Result. * @return An instance of Result in the Error state. * @since 1.0.0 */ public static Result error(Object error) { return from(null, Objects.requireNonNull(error)); } /** * Creates a new {@link Result} in the Empty state. * * @param The type of the Result. * @return An instance of Result in the Empty state. * @since 1.0.0 */ @SuppressWarnings("unchecked") public static Result empty() { return (Result) EMPTY; } // Checks /** * If the Result is of an Empty state. * * @return true if both {@link #isError()} and {@link #isSuccess()} return false. * @since 1.0.0 */ public boolean isEmpty() { return this == EMPTY; } /** * If the Result is of an Success state. * * @return true if both {@link #isError()} and {@link #isEmpty()} return false. * @since 1.0.0 */ public boolean isSuccess() { return this.element != null; } /** * If the Result is of an Error state. * * @return true if both {@link #isEmpty()} and {@link #isSuccess()} return false. * @since 1.0.0 */ public boolean isError() { return this.error != null; } /** * If the Result is of an Error state and the error is the specified {@link Class} * * @param clazz The class to check against. * @return true if it's this Result is in an Error state and the current error is an instance of * the class. * @since 1.0.0 */ public boolean isError(Class clazz) { return isError() && clazz.isInstance(this.error); } // Methods for doing stuff, chaining /** * Executes code when the {@link Result} is in the Success state defined by {@link #isSuccess()} * returning true. The {@link Consumer} takes in the current element, which is guaranteed to be * never null by definition. * * @param cons The consumer to run. * @return The current instance, useful for chaining. * @since 1.0.0 */ public Result onSuccess(Consumer cons) { if (isSuccess()) { cons.accept(this.element); } return this; } /** * Executes code when the {@link Result} is in the Empty state defined by {@link #isEmpty()} * returning true. * * @param run The runnable to run. * @return The current instance, useful for chaining. * @since 1.0.0 */ public Result onEmpty(Runnable run) { if (isEmpty()) { run.run(); } return this; } /** * Executes code when the {@link Result} is in the Error state defined by {@link #isError(Class)} * returning true. The {@link Consumer} takes in the current error, which is guaranteed to be * never null by definition.
*
* Please note that if the Result was made by giving a subclass, such as {@link * java.io.ByteArrayInputStream} then calling this method using {@code InputStream.class} as the * class parameter will work. This will always work when using {@code Object.class} as long as the * Result is in an error state.
*
* Also please note that due to type erasure there is no way for the code to implicitly know the * type parameters of the error, or even if it takes type parameters, without creating a lot of * extra work for everyone involved. The way around this is to do something similar to {@code * r.onError(List.class, (List list) -> list.forEach(System.out::println));}. It's also * good to keep in mind that this will compile and run even if the type parameter is anything but * {@link String}. It'll error with a {@link ClassCastException} when you try and do anything that * is specific to String, rather than generic to {@link Object}. Please contact any library * developer that doesn't specify all possible errors so you don't have to guess. * * @param cons The consumer to run. * @param clazz The class to run on. * @return The current instance, useful for chaining. * @since 1.0.0 */ @SuppressWarnings("unchecked") public Result onError(Class clazz, Consumer cons) { if (isError(clazz)) { cons.accept((U) this.error); } return this; } // Functional methods for further control /** * Filters the current {@link Result}. The passed {@link Predicate} will only be called when the * Result is in a Success state. If the filter returns true then this will return the current * instance, otherwise it'll return empty. In any other state the current instance is returned. * * @param filter The predicate to test against. * @return An empty Result if the filter returns false and the current Result is in a Success * state, otherwise the current instance. * @since 1.0.0 */ public Result filter(Predicate filter) { return isSuccess() && !filter.test(element) ? empty() : this; } /** * Maps the current {@link Result} to a new Result. The passed {@link Function} will only be * called when the Result is in a Success state. If the function returns null this will return an * empty instance, otherwise it returns a new Result with the returned element. In any other state * a new Result is returned with the same information. * * @param func The function to map from the current element to a new one. * @param The type of the new Result. * @return If the current Result is in the Success state, new Result in the Empty state if the * function returns null, or a Success state with the new element. In any other state a new * Result is returned with the same information. * @since 1.0.0 */ public Result map(Function func) { if (isEmpty()) { return empty(); } else if (isSuccess()) { return ofNullable(func.apply(this.element)); } else { // IntelliJ disagrees but it's not empty or a success, it's an error. return error( Objects.requireNonNull( this.error, "Should never happen. If this ever happens the world is ending.")); } } /** * Flatmaps the current {@link Result} to a new Result. The passed {@link Function} will only be * called when the Result is in a Success state. * * @param func The function to map from the current element to a new one. * @param The type of the new Result. * @return If the current Result is in the Success state, the Result returned from the function. * In any other state a new Result is returned with the same information. * @since 1.0.0 */ public Result flatMap(Function> func) { if (isEmpty()) { return empty(); } else if (isSuccess()) { return func.apply(this.element); } else { return error( Objects.requireNonNull( this.error, "Should never happen. If this ever happens the world is ending.")); } } /** * Retrieves the current element from this {@link Result} if it's in a Success state, otherwise * errors. * * @return The non-null element. * @throws NoSuchElementException If {@link #isSuccess()} returns false. * @since 1.0.0 */ public T get() { return orElseThrow(() -> new NoSuchElementException("Get call on non-successful Result.")); } /** * Retrieves the current error from this {@link Result} if it's in an Error state, otherwise * errors. * * @return The non-null error. * @throws NoSuchElementException if {@link #isError()} return false; * @since 1.1.0 */ public Object getError() { if (!isError()) { throw new NoSuchElementException("Get error call on non-error Result"); } return this.error; } /** * Retrieves the current error from this {@link Result} if it's in an Error state if * the error is of the passed class, otherwise errors. * * @param clazz The class to check against. * @return An {@link Optional} containing the error if it exists and is of the passed class, * otherwise an empty Optional. * @throws NoSuchElementException if {@link #isError(Class)} return false; * @since 1.1.0 */ public Optional getError(Class clazz) { return Optional.of(getError()) .filter(obj -> isError(clazz)) .map(clazz::cast); } /** * Retrieves the current element from this {@link Result} if it's in a success state, otherwise * returns the passed parameter. It should be noted that while this is marked as nullable, it's * not possible for this to return null unless you pass {@code null} to the parameter. * * @param other The value to default to. * @return The current value, or the parameter passed if this Result isn't in a Success state. * @since 1.0.0 */ @Nullable public T orElse(@Nullable T other) { return isSuccess() ? get() : other; } /** * Retrieves the current element from this {@link Result} if it's in a success state, otherwise * returns the result of the passed {@link Supplier}. It should be noted that while this is marked * as nullable, it's not possible for this to return null unless you the supplier returns null. * * @param supplier The supplier to default to. * @return The current value, or the return value of the supplier passed if this Result isn't in a * Success state. * @since 1.0.0 */ @Nullable public T orElseGet(Supplier supplier) { return orElse(supplier.get()); } /** * Retrieves the current element from this {@link Result} if it's in a success state, otherwise * throws the supplied exception. * * @param supplier The supplier of the exception. * @param The element, useful for type checking. * @return The element. * @throws X If the Result is in a state other than Success. */ public T orElseThrow(Supplier supplier) throws X { if (isSuccess()) { return element; } throw supplier.get(); } // Overrides @Override public int hashCode() { return Objects.hash(this.element, this.error); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Result)) { return false; } Result other = (Result) obj; return Objects.equals(this.element, other.element) && Objects.equals(this.error, other.error); } @Override public String toString() { return String.format("Result[element=%s,error=%s]", this.element, this.error); } }