FunctionalValue.java

/*
 * Original work Copyright 2020 Vavr, http://vavr.io
 * Original licence :
 * Copyright 2020 Vavr, http://vavr.io
 *
 * 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.
 *
 *
 * Modified work in 2021 by Alexis "Bad_Pop" Vachard
 * FunctionalValue is a really simplified version of the vavr Value adapted for JCoinbase's needs.
 * For more information, please take a look at the https://www.vavr.io/
 */
package com.github.badpop.jcoinbase.control;

import io.vavr.CheckedFunction0;
import io.vavr.Lazy;
import io.vavr.collection.Iterator;
import io.vavr.collection.Stream;
import io.vavr.control.Option;
import io.vavr.control.Try;

import java.util.Objects;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * Call result is a simplified version of the vavr {@link io.vavr.Value} adapted to the JCoinbase
 * needs. For more information, please take a look at the <a href="https://www.vavr.io/">vavr
 * site</a>
 *
 * <p>Functional programming is all about values and transformation of values using functions. The
 * {@code FunctionalValue} type reflects the values in a functional setting. It can be seen as the
 * result of a partial function application. Hence the result may be undefined. If a value is
 * undefined, we say it is empty.
 *
 * <p>How the empty state is interpreted depends on the context, i.e. it may be <em>undefined</em>,
 * <em>failed</em>, <em>no elements</em>, etc.
 *
 * <p>Basic operations:
 *
 * <ul>
 *   <li>{@link #get()}
 *   <li>{@link #getOrElse(Object)}
 *   <li>{@link #getOrElse(Supplier)}
 *   <li>{@link #getOrElseThrow(Supplier)}
 *   <li>{@link #getOrElseTry(CheckedFunction0)}
 *   <li>{@link #getOrNull()}
 *   <li>{@link #map(Function)}
 * </ul>
 *
 * Iterable extensions:
 *
 * <ul>
 *   <li>{@link #contains(Object)}
 *   <li>{@link #exists(Predicate)}
 *   <li>{@link #forAll(Predicate)}
 *   <li>{@link #forEach(Consumer)}
 *   <li>{@link #iterator()}
 * </ul>
 *
 * Side-effects:
 *
 * <ul>
 *   <li>{@link #peek(Consumer)}
 * </ul>
 *
 * Tests:
 *
 * <ul>
 *   <li>{@link #isEmpty()}
 * </ul>
 *
 * @param <T> The type of the wrapped value.
 */
@SuppressWarnings("unchecked")
public interface FunctionalValue<T> extends Iterable<T> {

  /**
   * Shortcut for {@code exists(e -> Objects.equals(e, element))}, tests if the given {@code
   * element} is contained.
   *
   * @param element An Object of type A, may be null.
   * @return true, if element is contained, false otherwise.
   */
  default boolean contains(T element) {
    return exists(e -> Objects.equals(e, element));
  }

  /**
   * Checks, if an element exists such that the predicate holds.
   *
   * @param predicate A Predicate
   * @return true, if predicate holds for one or more elements, false otherwise
   * @throws NullPointerException if {@code predicate} is null
   */
  default boolean exists(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate, "predicate is null");
    for (T t : this) {
      if (predicate.test(t)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Checks if this {@code Value} is asynchronously (short: async) computed.
   *
   * <p>Methods of a {@code Value} instance that operate on the underlying value may block the
   * current thread until the value is present and the computation can be performed.
   *
   * @return true if this {@code Value} is async (like {@link io.vavr.concurrent.Future}), false
   *     otherwise.
   */
  default boolean isAsync() {
    return false;
  }

  /**
   * Checks if this {@code Value} is lazily evaluated.
   *
   * @return true if this {@code Value} is lazy (like {@link Lazy} and {@link Stream}), false
   *     otherwise.
   */
  default boolean isLazy() {
    return false;
  }

  /**
   * States whether this is a single-valued type.
   *
   * @return {@code true} if this is single-valued, {@code false} otherwise.
   */
  default boolean isSingleValued() {
    return true;
  }

  /**
   * Checks, if the given predicate holds for all elements.
   *
   * @param predicate A Predicate
   * @return true, if the predicate holds for all elements, false otherwise
   * @throws NullPointerException if {@code predicate} is null
   */
  default boolean forAll(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate, "predicate is null");
    return !exists(predicate.negate());
  }

  /**
   * Gets the underlying functionalValue or throws if no functionalValue is present.
   *
   * <p><strong>IMPORTANT! This method will throw an undeclared {@link Throwable} if {@code
   * isEmpty() == true} is true.</strong>
   *
   * <p>Because the 'empty' state indicates that there is no functionalValue present that can be
   * returned, {@code get()} has to throw in such a case. Generally, implementing classes should
   * throw a {@link java.util.NoSuchElementException} if {@code isEmpty()} returns true.
   *
   * <p>However, there exist use-cases, where implementations may throw other exceptions. See {@link
   * Try#get()}.
   *
   * <p><strong>Additional note:</strong> Dynamic proxies will wrap an undeclared exception in a
   * {@link java.lang.reflect.UndeclaredThrowableException}.
   *
   * @return the underlying functionalValue if this is not empty, otherwise {@code get()} throws a
   *     {@code Throwable}
   */
  T get();

  /**
   * Returns the underlying functionalValue if present, otherwise {@code other}.
   *
   * @param other An alternative functionalValue.
   * @return A functionalValue of type {@code T}
   */
  default T getOrElse(T other) {
    return isEmpty() ? other : get();
  }

  /**
   * Returns the underlying functionalValue if present, otherwise {@code other}.
   *
   * @param supplier An alternative functionalValue supplier.
   * @return A functionalValue of type {@code T}
   * @throws NullPointerException if supplier is null
   */
  default T getOrElse(Supplier<? extends T> supplier) {
    Objects.requireNonNull(supplier, "supplier is null");
    return isEmpty() ? supplier.get() : get();
  }

  /**
   * Returns the underlying functionalValue if present, otherwise throws {@code supplier.get()}.
   *
   * @param <X> a Throwable type
   * @param supplier An exception supplier.
   * @return A functionalValue of type {@code T}.
   * @throws NullPointerException if supplier is null
   * @throws X if no functionalValue is present
   */
  default <X extends Throwable> T getOrElseThrow(Supplier<X> supplier) throws X {
    Objects.requireNonNull(supplier, "supplier is null");
    if (isEmpty()) {
      throw supplier.get();
    } else {
      return get();
    }
  }

  /**
   * Returns the underlying functionalValue if present, otherwise returns the result of {@code
   * Try.of(supplier).get()}.
   *
   * @param supplier An alternative functionalValue supplier.
   * @return A functionalValue of type {@code T}.
   * @throws NullPointerException if supplier is null
   */
  default T getOrElseTry(CheckedFunction0<? extends T> supplier) {
    Objects.requireNonNull(supplier, "supplier is null");
    return isEmpty() ? Try.of(supplier).get() : get();
  }

  /**
   * Returns the underlying functionalValue if present, otherwise {@code null}.
   *
   * @return A functionalValue of type {@code T} or {@code null}.
   */
  default T getOrNull() {
    return isEmpty() ? null : get();
  }

  /**
   * Checks, this {@code Value} is empty, i.e. if the underlying functionalValue is absent.
   *
   * @return false, if no underlying functionalValue is present, true otherwise.
   */
  boolean isEmpty();

  /**
   * Maps the underlying functionalValue to a different component type.
   *
   * @param mapper A mapper
   * @param <U> The new component type
   * @return A new functionalValue
   */
  <U> FunctionalValue<U> map(Function<? super T, ? extends U> mapper);

  /**
   * Performs the given {@code action} on the first element if this is an <em>eager</em>
   * implementation. Performs the given {@code action} on all elements (the first immediately,
   * successive deferred), if this is a <em>lazy</em> implementation.
   *
   * @param action The action that will be performed on the element(s).
   * @return this instance
   */
  FunctionalValue<T> peek(Consumer<? super T> action);

  /**
   * Returns a rich {@code io.vavr.collection.Iterator}.
   *
   * @return A new Iterator
   */
  @Override
  Iterator<T> iterator();

  /**
   * Converts this to an {@link java.util.Optional}.
   *
   * <pre>{@code
   * // = Optional.empty
   * Future.of(() -> { throw new Error(); })
   *       .toJavaOptional()
   *
   * // = Optional[ok]
   * Try.of(() -> "ok")
   *     .toJavaOptional()
   *
   * // = Optional[1]
   * List.of(1, 2, 3)
   *     .toJavaOptional()
   * }</pre>
   *
   * @return A new {@link java.util.Optional}.
   */
  default Optional<T> toJavaOptional() {
    return isEmpty() ? Optional.empty() : Optional.ofNullable(get());
  }

  /**
   * Converts this to a {@link CallResult}.
   *
   * @param leftSupplier A {@link Supplier} for the failure value for the {@link CallResult}
   * @param <L> Validation error component type
   * @return A new {@link CallResult}.
   */
  default <L> CallResult<L, T> toCallResult(Supplier<? extends L> leftSupplier) {
    Objects.requireNonNull(leftSupplier, "leftSupplier is null");
    if (this instanceof CallResult) {
      return ((CallResult<?, T>) this).mapLeft(ignored -> leftSupplier.get());
    } else {
      return isEmpty() ? CallResult.failure(leftSupplier.get()) : CallResult.success(get());
    }
  }

  /**
   * Converts this to a {@link CallResult}.
   *
   * @param left A failure value for the {@link CallResult}
   * @param <L> CallResult failure component type
   * @return A new {@link CallResult}.
   */
  default <L> CallResult<L, T> toCallResult(L left) {
    if (this instanceof CallResult) {
      return ((CallResult<?, T>) this).mapLeft(ignored -> left);
    } else {
      return isEmpty() ? CallResult.failure(left) : CallResult.success(get());
    }
  }

  /**
   * Converts this to an {@link Option}.
   *
   * @return A new {@link Option}.
   */
  default Option<T> toOption() {
    if (this instanceof Option) {
      return (Option<T>) this;
    } else {
      return isEmpty() ? Option.none() : Option.some(get());
    }
  }

  @Override
  default Spliterator<T> spliterator() {
    return Spliterators.spliterator(
        iterator(),
        isEmpty() ? 0 : 1,
        Spliterator.IMMUTABLE | Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED);
  }

  /**
   * Clarifies that functionalValues have a proper equals() method implemented.
   *
   * <p>See <a
   * href="https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#equals-java.lang.Object-">Object.equals(Object)</a>.
   *
   * @param o An object
   * @return true, if this equals o, false otherwise
   */
  @Override
  boolean equals(Object o);

  /**
   * Clarifies that functionalValues have a proper hashCode() method implemented.
   *
   * <p>See <a
   * href="https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode--">Object.hashCode()</a>.
   *
   * @return The hashcode of this object
   */
  @Override
  int hashCode();

  /**
   * Clarifies that functionalValues have a proper toString() method implemented.
   *
   * <p>See <a
   * href="https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#toString--">Object.toString()</a>.
   *
   * @return A String representation of this object
   */
  @Override
  String toString();
}