Result.java

package org.klojang.check.extra;

import org.klojang.check.Check;
import org.klojang.check.fallible.FallibleConsumer;

import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Supplier;

/**
 * A simple value container that explicitly allows the value to be {@code null}. This class is meant to be
 * used as the return value of methods that would otherwise return {@code null} <b>both</b> as the legitimate
 * outcome of a computation <b>and</b> as a signal that the computation yielded no result. The
 * {@link java.util.HashMap HashMap} class is a well-known example. If its {@code get()} method returns
 * {@code null}, it is not clear whether the requested key was absent, or whether it was present, but
 * associated with value {@code null}. If you wanted to create a {@code Map} implementation where this
 * distinction is clear, you could use the {@code Result} class and return {@link Result#notAvailable()} if
 * the requested key was absent, and {@code Result.of(null)} (or {@link Result#nullResult()}) if present but
 * {@code null}.
 *
 * @param <T> the type of the result value
 */
public final class Result<T> {

  private static final Result<?> NULL = new Result<>(null);
  private static final Result<?> NONE = new Result<>(new Object());

  /**
   * Returns a {@code Result} containing the specified value (possibly {@code null}).
   *
   * @param value The value
   * @param <T> The type of the result value
   * @return a {@code Result} containing the specified value
   */
  @SuppressWarnings("unchecked")
  public static <T> Result<T> of(T value) {
    if (value == null) {
      return (Result<T>) NULL;
    }
    return new Result<>(value);
  }

  /**
   * Returns a special {@code Result} instance indicating the absence of a result.
   *
   * @param <T> the type of the result value
   * @return a special {@code Result} object indicating the absence of a result
   */
  @SuppressWarnings("unchecked")
  public static <T> Result<T> notAvailable() {
    return (Result<T>) NONE;
  }

  /**
   * Returns a {@code Result} containing {@code null}.
   *
   * @param <T> the type of the result value
   * @return a {@code Result} containing {@code null}
   */
  @SuppressWarnings("unchecked")
  public static <T> Result<T> nullResult() {
    return (Result<T>) NULL;
  }

  private final T val;

  private Result(T value) {
    this.val = value;
  }

  /**
   * Returns the result. You should have established first that a result value
   * {@linkplain #isAvailable() is available} or a {@link NoSuchElementException} will be thrown.
   *
   * @return the value
   * @throws NoSuchElementException if no result is available
   */
  public T get() throws NoSuchElementException {
    if (this != NONE) {
      return val;
    }
    throw noResult();
  }

  /**
   * Returns {@code true} if the operation that produced this {@code Result} successfully computed the result.
   * If so, the result value can be retrieved via the {@link #get()} method. If not, calling {@code get()}
   * method will result in a {@link NoSuchElementException}.
   *
   * @return {@code true} if a result could be computed
   */
  public boolean isAvailable() {
    return this != NONE;
  }

  /**
   * Returns {@code true} if the operation that produced this {@code Result} could not compute a result.
   *
   * @return {@code true} if the operation that produced this {@code Result} could not compute a result
   */
  public boolean isUnavailable() {
    return this == NONE;
  }

  /**
   * Returns {@code true} if the operation that produced this {@code Result} could not compute a result or the
   * result was {@code null}.
   *
   * @return {@code true} if a result could either not be computed or it was {@code null}
   */
  public boolean isUnavailableOrNull() {
    return this == NONE || this == NULL;
  }

  /**
   * Returns {@code true} if the operation that produced this {@code Result} successfully computed a result
   * and the result value was {@code null}.
   *
   * @return {@code true} if a result could be computed, and it turned out to be {@code null}
   */
  public boolean isAvailableAndNull() {
    return this == NULL;
  }

  /**
   * Returns {@code true} if the operation that produced this {@code Result} successfully computed a result
   * and the result value was not {@code null}.
   *
   * @return {@code true} if a result could be computed and it was a non-{@code null} result
   */
  public boolean isAvailableAndNotNull() {
    return this != NONE && this != NULL;
  }

  /**
   * If available, passes the result to the specified consumer; else does nothing.
   *
   * @param consumer the consumer of the result
   * @param <X> the type of the exception thrown by the consumer
   * @throws X if the consumer experiences an error
   */
  public <X extends Throwable> void ifAvailable(FallibleConsumer<T, X> consumer) throws X {
    Check.notNull(consumer);
    if (isAvailable()) {
      consumer.accept(val);
    }
  }

  /**
   * Returns the result value, if available, else the provided default value.
   *
   * @param defaultValue the default value
   * @return the result value, if available, else the provided default value
   */
  public T orElse(T defaultValue) {
    return isAvailable() ? val : defaultValue;
  }

  /**
   * Returns this {@code Result} if available, else the provided {@code Result}.
   *
   * @param supplier the value to return if this {@code Result} is {@link Result#notAvailable()}.
   * @return this instance's value if available; else the value provided by the specified {@code Supplier};
   * @throws IllegalArgumentException if the specified {@code Result} is {@code Result.notAvailable()}
   */
  public T orElseGet(Supplier<T> supplier) throws IllegalArgumentException {
    Check.notNull(supplier);
    return isAvailable() ? val : supplier.get();
  }

  /**
   * Returns {@code true} if the specified object is a {@code Result} that either is
   * <i>this</i> {@code Result} or contains the same value.
   *
   * @param obj the object to compare this instance with
   * @return whether this instance equals the specified object.
   */
  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    return obj instanceof Result<?> that && Objects.equals(this.val, that.val);
  }

  /**
   * Returns the hashcode of the value contained in this {@code Result}, or 0 if no result was available.
   *
   * @return the hashcode of the value contained in this {@code Result}, or 0 if no result was available
   */
  @Override
  public int hashCode() {
    return Objects.hashCode(val);
  }

  /**
   * Returns a string representation analogous to the one provided by {@link java.util.Optional}.
   *
   * @return a string representation analogous to the one provided by {@link java.util.Optional}
   */
  @Override
  public String toString() {
    return this != NONE ? String.format("Result[%s]", val) : "Result.notAvailable";
  }

  private static NoSuchElementException noResult() {
    return new NoSuchElementException("no result available");
  }

}