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

com.kenshoo.pl.entity.Triptional Maven / Gradle / Ivy

Go to download

A Java persistence layer based on JOOQ for high performance and business flow support.

There is a newer version: 0.1.121-jooq-3.16.3
Show newest version
package com.kenshoo.pl.entity;

import org.apache.commons.lang3.builder.HashCodeBuilder;

import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.*;

import static com.kenshoo.pl.entity.Triptional.State.*;
import static java.util.Objects.requireNonNull;

/**
 * A container object which has three possible distinct states:
 * 
    *
  • Present with a non-{@code null} value
  • *
  • Present with a {@code null} value
  • *
  • Absent
  • *
* * This three-state object can be understood as an extension or generalization of {@code Optional}, * which allows one to distinguish between a "present and {@code null}" value and an "absent" value. * *

Similar to {@code Optional}, this is a value-based * class; use of identity-sensitive operations (including reference equality * ({@code ==}), identity hash code, or synchronization) on instances of * {@code Triptional} may have unpredictable results and should be avoided. * * The motivation for creating this type is as follows: *

* In the persistence-layer we manage objects representing DB values, and since the DB supports {@code NULL} as a valid field value, * this resulted in two types that require a three-state representation of fields: *

    *
  1. In {@link Entity} which holds fields fetched from the DB, a field can be either: *
      *
    • Fetched with a non-{@code null} value
    • *
    • Fetched with a {@code null} value
    • *
    • Not fetched
    • *
    *
  2. *
  3. In {@link EntityChange} (command) a field can be either: *
      *
    • Changed by the command to a non-{@code null} value
    • *
    • Changed by the command to a {@code null} value
    • *
    • Not in the command (unchanged)
    • *
    *
* * @param the type of value in the {@code Triptional} */ public class Triptional { enum State { NOT_NULL, NULL, ABSENT } private static final Triptional NULL_INSTANCE = new Triptional<>(NULL); private static final Triptional ABSENT_INSTANCE = new Triptional<>(ABSENT); private final T value; private final State state; private Triptional(final State state) { this(null, state); } private Triptional(final T value, final State state) { this.value = value; this.state = state; } /** * Returns a {@code Triptional} with the specified present value, can be {@code null}. * * @param the class of the value * @param value the value to be present, can be {@code null} * @return a {@code Triptional} with the value present */ public static Triptional of(final T value) { return value == null ? nullInstance() : new Triptional<>(value, NOT_NULL); } /** * Returns an {@code null} {@code Triptional} instance - meaning that it is present but with a {@code null} value.
* * Note Though it may be tempting to do so, avoid testing if an object * is {@code null} by comparing with {@code ==} against instances returned by * {@code Triptional.nullInstance()}. There is no guarantee that it is a singleton. * Instead, use {@link #isNull()}. * * @param Type of the {@code null} value * @return a {@code null} {@code Triptional} */ @SuppressWarnings("unchecked") public static Triptional nullInstance() { return (Triptional) NULL_INSTANCE; } /** * Returns an absent {@code Triptional} instance. No value is present for this * Triptional. * * Note Though it may be tempting to do so, avoid testing if an object * is empty by comparing with {@code ==} against instances returned by * {@code Triptional.absent()}. There is no guarantee that it is a singleton. * Instead, use either {@link #isPresent()} or {@link #isAbsent()}. * * @param Type of the absent value * @return an absent {@code Triptional} */ @SuppressWarnings("unchecked") public static Triptional absent() { return (Triptional) ABSENT_INSTANCE; } /** * If a value is present in this {@code Triptional} (including possibly {@code null}), returns the value, * otherwise throws {@code NoSuchElementException}. * * @return the value held by this {@code Triptional}, may be {@code null} * @throws NoSuchElementException if there is no value present * * @see Triptional#isPresent() */ public T get() { if (isAbsent()) { throw new NoSuchElementException("No value present"); } return value; } /** * If a value is present and not-{@code null}, invoke the specified consumer with the value, * otherwise do nothing. * * @param consumer block to be executed if a value is present and not-{@code null} */ public void ifNotNull(final Consumer consumer) { if (value != null) { consumer.accept(value); } } /** * If a value is present and not-{@code null}, apply the mapper() function to it, * and return a {@code Triptional} describing the result.
* If a value is present and {@code null}, return a {@code null} Triptional instance.
* Otherwise - return an absent {@code Triptional}.
* This method is similar to {@link Optional#map} except that it will preserve a present {@code null} value as well * (will not turn it into an absent Triptional} * * @param The type of the result of the mapping function * @param mapper a mapping function to apply to the value, if present and not-{@code null} * @return a {@code Triptional} describing the result of applying the mapper, if it is present and not-{@code null}; * or a null {@code Triptional}, if it is present and {@code null}; * otherwise - an absent {@code Triptional} * @throws NullPointerException if the mapper function is {@code null} */ public Triptional map(final Function mapper) { return map(mapper, () -> null); } /** * If a value is present and not-{@code null}, apply the notNullMapper() function to it, * and return a {@code Triptional} describing the result.
* If a value is present and {@code null}, call the nullReplacer() function, * and return a {@code Triptional} holding the result.
* Otherwise - return an absent {@code Triptional}. * * Note This method is similar to {@link Optional#map} except that it also allows for a separately-defined replacement operation * in case of a present {@code null} value. This is convenient in case some default value is required to replace the {@code null}.
* For example - the following code will convert numbers into their string representations, unless the number is {@code null}, * in which case it will be replaced with an empty string: * *
{@code
     *     final Integer number = readNumber();
     *     final Triptional triptional = Triptional.of(number).map(String::valueOf, () -> "");
     * }
* * @param The type of the result of the mapping function * @param notNullMapper a mapping function to apply to the value, if present and not-{@code null} * @param nullReplacer a function to calculate a replacement value, if present and {@code null} * @return a {@code Triptional} describing the result of applying the notNullMapper * function to the value of this {@code Triptional}, if it is present and not-{@code null}; * or a {@code Triptional} holding the result of the nullReplacer function, if present and {@code null}; * otherwise - an absent {@code Triptional} * @throws NullPointerException if the notNullMapper function is {@code null} or the nullReplacer function is {@code null} */ public Triptional map(final Function notNullMapper, final Supplier nullReplacer) { requireNonNull(notNullMapper, "notNullMapper is required"); requireNonNull(nullReplacer, "nullReplacer is required"); switch (state) { case NOT_NULL: return of(notNullMapper.apply(value)); case NULL: return of(nullReplacer.get()); default: return absent(); } } /** * If a value is present and not-{@code null}, apply the Triptional-bearing notNullMapper() function to it, * and return that result.
* If a value is present and {@code null}, return a {@code null} {@code Triptional} .
* Otherwise - return an absent {@code Triptional}.
* * This method is similar to {@link Optional#flatMap} except that it will preserve a present {@code null} value as well * (will not turn it into an absent {@code Triptional}} * * @param The type of the result of the mapping function * @param mapper a Triptional-bearing mapping function to apply to the value, if present and value not-{@code null} * @return the result of applying the notNullMapper function, if present and value not-{@code null}; * or a {@code null} {@code Triptional}, if present and value is {@code null}; * otherwise - an absent {@code Triptional} * @throws NullPointerException if the mapper function is {@code null}, or if it returns a {@code null} result */ public Triptional flatMap(final Function> mapper) { return flatMap(mapper, Triptional::nullInstance); } /** * If a value is present and not-{@code null}, apply the Triptional-bearing notNullMapper() function to it, * and return that result.
* If a value is present and {@code null}, call the nullReplacer() Triptional-bearing function, * and return that result.
* Otherwise - return an absent {@code Triptional}. * * Note This method is similar to {@link Optional#flatMap} except that it also allows for a separately-defined replacement * for a {@code null} value. This is convenient in case some operation requires a default value to replace the {@code null}.
* For example - the following code converts a nested Triptional holding a number into a single one, unless the inner object is a {@code null} Triptional, * in which case it will be replaced by a zero: * *
{@code
     *     final Triptional> nested = readNestedTriptional();
     *     final Triptional flattened = nested.flatMap(Function.identity(), () -> 0);
     * }
* * @param The type of the result of the mapping function * @param notNullMapper a Triptional-bearing mapping function to apply to the value, if present and value not-{@code null} * @param nullReplacer a Triptional-bearing function to calculate a replacement instance, if present and value is {@code null} * @return the result of applying the notNullMapper function, if present and value not-{@code null}; * or the result of calling the nullReplacer function, if present and value is {@code null}; * otherwise - an absent {@code Triptional} * @throws NullPointerException if either of the input functions are {@code null}, or if either one of them returns a {@code null} result */ public Triptional flatMap(final Function> notNullMapper, final Supplier> nullReplacer) { requireNonNull(notNullMapper, "notNullMapper is required"); requireNonNull(nullReplacer, "nullReplacer is required"); switch (state) { case NOT_NULL: return requireNonNull(notNullMapper.apply(value)); case NULL: return requireNonNull(nullReplacer.get()); default: return absent(); } } /** * Returns an {code Optional} with the value, if present and not-{@code null}; otherwise (whether present with a {@code null} value or absent) - * return an empty {@code Optional}.
* This method is in effect a "reduction" operation where two distinct states of {@code Triptional}, {@code null} and absent, are mapped to the same empty {@code Optional}. * * @return an {code Optional} with a value, if present and not-{@code null}, otherwise - an empty {@code Optional} */ public Optional asOptional() { return Optional.ofNullable(value); } /** * If a value is present and not-{@code null}, apply the notNullMapper() function to it, * and return a {@code Optional} holding the result, or an empty {@code Optional} if the result is {@code null}.
* Otherwise - return an empty {@code Optional}. * * @param The type of the result of the mapping function * @param mapper a mapping function to apply to the value, if present and not-{@code null} * @return an {@code Optional} holding the result of applying the mapper * function to the value of this {@code Triptional}, if it is present and not-{@code null}; * otherwise - an empty {@code Optional} * @throws NullPointerException if the mapper function is {@code null} */ public Optional mapToOptional(final Function mapper) { return mapToOptional(mapper, () -> null); } /** * If a value is present and not-{@code null}, apply the notNullMapper() function to it, * and return a {@code Optional} holding the result, or an empty {@code Optional} if the result is {@code null}.
* If a value is present and {@code null}, call the nullReplacer() function, * and return an {@code Optional} holding the result, or an empty {@code Optional} if the result is {@code null}.
* Otherwise - return an empty {@code Optional}. * * @param The type of the result of the mapping function * @param notNullMapper a mapping function to apply to the value, if present and not-{@code null} * @param nullReplacer a function to calculate a replacement value, if present and {@code null} * @return an {@code Optional} holding the result of applying the notNullMapper * function to the value of this {@code Triptional}, if it is present and not-{@code null}; * or an {@code Optional} holding the result of the nullReplacer function, if present and {@code null}; * otherwise - an empty {@code Optional} * @throws NullPointerException if the notNullMapper function is {@code null} or the nullReplacer function is {@code null} */ public Optional mapToOptional(final Function notNullMapper, final Supplier nullReplacer) { return map(notNullMapper, nullReplacer).asOptional(); } /** * If a value is present, and the value matches the given predicate, * returns an {@code Triptional} describing the value, otherwise returns an * absent {@code Triptional}. * * @param predicate the predicate to apply to a value, if present * @return an {@code Triptional} describing the value of this * {@code Triptional}, if a value is present and the value matches the * given predicate; otherwise an absent {@code Triptional} * @throws NullPointerException if the predicate is {@code null} */ public Triptional filter(final Predicate predicate) { requireNonNull(predicate, "a predicate must be provided"); if (matches(predicate)) { return this; } return absent(); } /** * Return {@code true} if there is a value is present, and it matches the given predicate; * otherwise return {@code false} * * @param predicate the predicate to apply to a value, if present * @return {@code true} if there is a value is present, and it matches the given predicate; * otherwise {@code false} * @throws NullPointerException if the predicate is {@code null} */ public boolean matches(final Predicate predicate) { requireNonNull(predicate, "a predicate must be provided"); return isPresent() && predicate.test(value); } /** * Return {@code true} if there is a value present, otherwise {@code false}. * * @return {@code true} if there is a value present, otherwise {@code false} */ public boolean isPresent() { return !isAbsent(); } /** * Return {@code true} if there is no value present, otherwise {@code false}. * * @return {@code true} if there is no value present, otherwise {@code false}. */ public boolean isAbsent() { return state == ABSENT; } /** * Return {@code true} if the value is present and not {@code null}, otherwise {@code false}. * * @return {@code true} if the value is present and not {@code null}, otherwise {@code false}. */ public boolean isNotNull() { return state == NOT_NULL; } /** * Return {@code true} if the value is present and {@code null} or no value, otherwise {@code false}. * * @return {@code true} if the value is present and {@code null} or no value, otherwise {@code false}. */ public boolean isNullOrAbsent() { return isAbsent() || isNull(); } /** * Return {@code true} if the value is present and {@code null}, otherwise {@code false}. * * @return {@code true} if the value is present and {@code null}, otherwise {@code false}. */ public boolean isNull() { return state == NULL; } /** * Return {@code true} if there is a value present (possibly {@code null}), and it equals the input; * otherwise {@code false} * * @param value a value to compare to this value * @return {@code true} if there is a value present, and it equals the input; otherwise {@code false} */ public boolean equalsValue(final T value) { return isPresent() && Objects.equals(this.value, value); } /** * Indicates whether some other object is "equal to" this {@code Triptional}. The * other object is considered equal if: *
    *
  • it is also a {@code Triptional} and;
  • *
  • both instances have no value present or;
  • *
  • both instances have a present and {@code null} value or;
  • *
  • the present values are "equal to" each other via {@code equals()}.
  • *
* * @param obj an object to be tested for equality * @return {@code true} if the other object is "equal to" this object * otherwise {@code false} */ @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals(final Object obj) { return equals(obj, Objects::equals); } /** * Indicates whether some other object is "equal to" this {@code Triptional} using the input function to test equality.
* The other object is considered equal if: *
    *
  • it is also a {@code Triptional} and; *
  • both instances have no value present or; *
  • both instances have a value present (possibly {@code null}), * and when the valueEqualityFunction is applied to both values it returns {@code true} *
* * @param obj an object to be tested for equality * @param valueEqualityFunction the function to use for testing equality of values, when present * @return {@code true} if the other object is "equal to" this object * otherwise {@code false} * @throws NullPointerException if valueEqualityFunction is {@code null} */ public boolean equals(final Object obj, final BiFunction valueEqualityFunction) { requireNonNull(valueEqualityFunction, "A value equality function must be provided"); if (this == obj) { return true; } if (!(obj instanceof Triptional)) { return false; } //noinspection unchecked final Triptional other = (Triptional) obj; if (!(isPresent() && other.isPresent())) { return false; } return valueEqualityFunction.apply(value, other.value); } /** * Returns a hash code value which is the combination of: *
    *
  • The present value, if any, or 0 (zero) if no value is present.
  • *
  • An internal indicator to differentiate between a present and an absent value * (without this, a present value of {@code null} and an absent value would produce the same hashcode)
  • *
* * @return hash code value of this instance */ @Override public int hashCode() { return new HashCodeBuilder() .append(state) .append(value) .toHashCode(); } /** * Returns a non-empty string representation of this {@code Triptional} suitable for * debugging. The exact presentation format is unspecified and may vary * between implementations and versions.
* * Implementation Note: If a value is present the result must include its string * representation in the result. Absent, {@code null} and not-{@code null} Triptionals must all be * unambiguously differentiable. * * @return the string representation of this instance */ @Override public String toString() { switch (state) { case NOT_NULL: return String.format("Triptional[%s]", value); case NULL: return "Triptional.null"; default: return "Triptional.absent"; } } }