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

io.github.oliviercailloux.jaris.exceptions.TryOptionalImpl Maven / Gradle / Ivy

package io.github.oliviercailloux.jaris.exceptions;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import io.github.oliviercailloux.jaris.throwing.TBiFunction;
import io.github.oliviercailloux.jaris.throwing.TConsumer;
import io.github.oliviercailloux.jaris.throwing.TFunction;
import io.github.oliviercailloux.jaris.throwing.TRunnable;
import io.github.oliviercailloux.jaris.throwing.TSupplier;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

/**
 * See this class in branch doc-try for some thoughts about possible extensions; and extensive
 * (draft!) documentation about the design choices.
 *
 * @param  the type of result possibly kept in this object.
 * @param  the type of cause kept in this object if it is a failure.
 */
abstract class TryOptionalImpl implements TryOptional {

  /**
   * A sort of try optional that guarantees that a success has a (non-{@code null}) associated
   * value. Is homeomorphic to a {@code T} xor {@code X}. Suitable for {@link Try} and
   * {@link TryCatchAll}, depending on the catching strategy. The name (“variable catch”) indicates
   * that this interface applies to both catching strategies.
   *
   * @param  the type of result kept in this object if it is a success.
   * @param  the type of cause kept in this object if it is a failure.
   * @param  a priori constraint applied to some functionals on the type of throwable that they
   *        may throw – when catching all, it sometimes makes sense to authorize functionals to
   *        throw {@code Throwable}; when catching species of exceptions, this makes no sense and we
   *        reduce possible signatures to clarify the intended use.
   */
  public interface TryVariableCatchInterface
      extends TryOptional {

    /**
     * Returns {@code true} iff this instance contains a result (and not a cause).
     *
     * @return {@code true} iff {@link #isFailure()} returns {@code false}
     */
    @Override
    public boolean isSuccess();

    /**
     * Return {@code true} iff this instance contains a cause (and not a result).
     *
     * @return {@code true} iff {@link #isSuccess()} returns {@code false}
     */
    @Override
    public boolean isFailure();

    /**
     * Returns the transformed result contained in this instance if it is a success, using the
     * provided {@code transformation}; or the transformed cause contained in this instance if it is
     * a failure, using the provided {@code causeTransformation}.
     * 

* This method necessarily applies exactly one of the provided functions. * * @param the type of transformed result to return * @param a type of exception that the provided functions may throw * @param transformation a function to apply to the result if this instance is a success * @param causeTransformation a function to apply to the cause if this instance is a failure * @return the transformed result or cause * @throws Y if the function that was applied threw an exception of type {@code Y} * @throws NullPointerException if the function that was applied returned {@code null} */ public U map( TFunction transformation, TFunction causeTransformation) throws Y; /** * Returns the result contained in this instance if it is a success, without applying the * provided function; or returns the transformed cause contained in this instance if it is a * failure, using the provided {@code causeTransformation}. *

* Equivalent to {@code map(t -> t, causeTransformation)}. * * @param a type of exception that the provided function may throw * @param causeTransformation the function to apply iff this instance is a failure * @return the result, or the transformed cause * @throws Y if the function was applied and threw an exception of type {@code Y} * @throws NullPointerException if the function was applied and returned {@code null} */ public T orMapCause(TFunction causeTransformation) throws Y; /** * Returns an optional containing the result of this instance, without invoking the given * consumer, if this try is a success; otherwise, invokes the given consumer and returns an * empty optional. * * @param a type of exception that the provided consumer may throw * @param consumer the consumer to invoke if this instance is a failure * @return an optional, containing the result if this instance is a success, empty otherwise * @throws Y if the consumer was invoked and threw an exception of type {@code Y} */ public Optional orConsumeCause(TConsumer consumer) throws Y; /** * Returns the result contained in this instance if this instance is a success, or throws the * cause contained in this instance. *

* Equivalent to: {@link #orThrow(Function) orThrow(t -> t)}. * * @return the result that this success contains * @throws X iff this instance is a failure */ public T orThrow() throws X; /** * Returns the result contained in this instance if this instance is a success, or throws the * transformed cause contained in this instance. * * @param the type of throwable to throw if this instance is a failure * @param causeTransformation the function to apply to the cause iff this instance is a failure * @return the result that this success contains * @throws Y if this instance is a failure * @throws NullPointerException if the provided function was applied and returned {@code null} */ public T orThrow(Function causeTransformation) throws Y; /** * Runs the runnable iff this instance is a success, and returns this instance if it succeeds * and the cause of failure if it throws a catchable throwable; otherwise, returns this * instance. * * @param runnable the runnable to invoke iff this instance is a success * @return a success iff this instance is a success and the provided runnable does not throw */ public TryVariableCatchInterface andRun(TRunnable runnable); /** * Runs the consumer iff this instance is a success, and returns this instance if it succeeds * and the cause of failure if it throws a catchable throwable; otherwise, returns this * instance. * * @param consumer the consumer to invoke iff this instance is a success * @return a success iff this instance is a success and the provided consumer does not throw */ public TryVariableCatchInterface andConsume(TConsumer consumer); /** * Applies the given mapper iff this instance is a success, and returns the transformed success * if it returns a non-{@code null} value, the cause of the failure if it throws a catchable * throwable (including possibly a {@link NullPointerException} cause if this is considered * catchable and the function returns {@code null}); otherwise, returns this instance. *

* Equivalent to {@code t.map(s -> Try.get(() -> mapper.apply(s)), t)}. * * @param the type of result that the returned try will be declared to contain * @param mapper the mapper to apply to the result iff this instance is a success * @return a success iff this instance is a success and the provided mapper does not throw */ public TryVariableCatchInterface andApply(TFunction mapper); /** * Returns {@code true} iff the given object is a {@code TryOptional}, this and the given object * are both successes and have equal results, or are both failures and have equal causes. * * @param o2 the object to compare this instance to * @return {@code true} iff {@code o2} is a {@code TryOptional} and this instance and {@code o2} * have present and equal results or present and equal causes */ @Override public boolean equals(Object o2); } /** * A sort of try optional that guarantees that a success has no associated value. Is homeomorphic * to an {@code Optional} (plus indication of catching checked or catching all). Suitable for * {@link TryVoid} and {@link TryCatchAllVoid}, depending on the catching strategy. The name * (“variable catch”) indicates that this interface applies to both catching strategies. * * @param the type of cause kept in this object if it is a failure. * @param a priori constraint applied to some functionals on the type of throwable that they * may throw (see {@link TryVariableCatchInterface}). */ public interface TryVariableCatchVoidInterface extends TryOptional { /** * Returns {@code true} iff this instance is a success, hence, contains no cause. * * @return {@code true} iff {@link #isFailure()} returns {@code false} */ @Override public boolean isSuccess(); /** * Return {@code true} iff this instance contains a cause. * * @return {@code true} iff {@link #isSuccess()} returns {@code false} */ @Override public boolean isFailure(); /** * Returns the supplied result if this instance is a success, using the provided * {@code supplier}; or the transformed cause contained in this instance if it is a failure, * using the provided {@code causeTransformation}; unless the invoked function returns * {@code null}. *

* This method necessarily invokes exactly one of the provided functional interfaces. * * @param the type of (supplied or transformed) result to return * @param a type of exception that the provided functions may throw * @param supplier a supplier to get a result from if this instance is a success * @param causeTransformation a function to apply to the cause if this instance is a failure * @return the supplied result or transformed cause * @throws Y iff the functional interface that was invoked threw an exception of type {@code Y} * @throws NullPointerException if the function that was applied returned {@code null} */ public T map(TSupplier supplier, TFunction causeTransformation) throws Y; /** * If this instance is a failure, invokes the given consumer using the cause contained in this * instance. If this instance is a success, do nothing. * * @param a type of exception that the provided consumer may throw * @param consumer the consumer to invoke if this instance is a failure * @throws Y iff the consumer was invoked and threw an exception of type {@code Y} */ public void ifFailed(TConsumer consumer) throws Y; /** * If this instance is a failure, throws the cause it contains. Otherwise, do nothing. *

* Equivalent to: {@link #orThrow(Function) orThrow(t -> t)}. * * @throws X iff this instance contains a cause */ public void orThrow() throws X; /** * If this instance is a failure, throws the transformed cause it contains. Otherwise, do * nothing. * * @param the type of throwable to throw if this instance is a failure * @param causeTransformation the function to apply to the cause iff this instance is a failure * @throws Y if this instance is a failure * @throws NullPointerException if the provided function was applied and returned {@code null} */ public void orThrow(Function causeTransformation) throws Y; /** * If this instance is a success, returns a try representing the result of invoking the given * supplier if it supplies a non-{@code null} result, and a failure if the supplier throws a * catchable throwable (or if the supplier returns {@code null} and a * {@link NullPointerException} is considered catchable); otherwise, returns this failure. *

* If this instance is a failure, it is returned, without invoking the given supplier. * Otherwise, the given supplier is invoked. If it supplies a non-{@code null} result, a success * is returned, containing the just supplied result. If the supplier throws a catchable * throwable, a failure is returned, containing the cause it threw. If the supplier returns * {@code null} and a {@link NullPointerException} is considered catchable, a failure is * returned, containing a {@link NullPointerException} as a cause. * * @param the type of result that the returned try will be declared to contain * @param supplier the supplier to attempt to get a result from if this instance is a success. * @return a success iff this instance is a success and the given supplier returned a * non-{@code null} result. */ public TryVariableCatchInterface andGet(TSupplier supplier); /** * If this instance is a success, returns a try void representing the result of invoking the * given runnable; otherwise, returns this failure. *

* If this instance is a failure, it is returned, without invoking the given runnable. * Otherwise, the given runnable is invoked. If it terminates without throwing, a success is * returned. If the runnable throws a catchable throwable, a failure is returned, containing the * cause it threw. * * @param runnable the runnable to attempt to run if this instance is a success. * @return a success iff this instance is a success and the given runnable terminated without * throwing. */ public TryVariableCatchVoidInterface andRun(TRunnable runnable); /** * Returns this instance if it is a success; otherwise, returns a try void representing the * result of invoking the given runnable. *

* If this instance is a success, it is returned, without invoking the given runnable. * Otherwise, the given runnable is invoked. If it terminates without throwing, a success is * returned. If the runnable throws a catchable throwable, a failure is returned, containing the * cause it threw. * * @param runnable the runnable to attempt to invoke if this instance is a failure. * @return a success iff this instance is a success or the given runnable terminated without * throwing. */ public TryVariableCatchVoidInterface or(TRunnable runnable); /** * Returns {@code true} iff the given object is a {@code TryOptional}, this and the given object * are both successes, or are both failures and have equal causes. * * @param o2 the object to compare this instance to * @return {@code true} iff {@code o2} is a {@code TryOptional} and this instance and {@code o2} * are both successes or have present and equal causes */ @Override public boolean equals(Object o2); } public abstract static class TryVariableCatch extends TryOptionalImpl implements TryVariableCatchInterface { @Override public T orMapCause(TFunction causeTransformation) throws Y { return map(t -> t, causeTransformation); } @Override public T orThrow() throws X { return orThrow(t -> t); } @Override public String toString() { final ToStringHelper stringHelper = MoreObjects.toStringHelper(this); orConsumeCause(e -> stringHelper.add("cause", e)) .ifPresent(r -> stringHelper.add("result", r)); return stringHelper.toString(); } } public abstract static class TryVariableCatchSuccess extends TryVariableCatch { protected final T result; protected TryVariableCatchSuccess(T result) { this.result = checkNotNull(result); } @Override public Optional getResult() { return Optional.of(result); } @Override public Optional getCause() { return Optional.empty(); } @Override public U map( TFunction transformation, TFunction causeTransformation) throws Y { final U transformed = transformation.apply(result); checkNotNull(transformed, "Transformation returned null"); return transformed; } @Override public Optional orConsumeCause(TConsumer consumer) throws Y { return Optional.of(result); } @Override public T orThrow(Function causeTransformation) { return result; } } public abstract static class TryVariableCatchFailure extends TryVariableCatch { protected final X cause; protected TryVariableCatchFailure(X cause) { this.cause = checkNotNull(cause); } @Override public Optional getResult() { return Optional.empty(); } @Override public Optional getCause() { return Optional.of(cause); } @Override public U map( TFunction transformation, TFunction causeTransformation) throws Y { final U transformed = causeTransformation.apply(cause); checkNotNull(transformed, "Cause transformation returned null"); return transformed; } @Override public Optional orConsumeCause(TConsumer consumer) throws Y { consumer.accept(cause); return Optional.empty(); } @Override public Object orThrow(Function causeTransformation) throws Y { final Y transformed = causeTransformation.apply(cause); checkNotNull(transformed, "Cause transformation returned null"); throw transformed; } } public abstract static class TryVariableCatchVoid extends TryOptionalImpl implements TryVariableCatchVoidInterface { @Override public Optional getResult() { return Optional.empty(); } @Override public void orThrow() throws X { orThrow(t -> t); } @Override public String toString() { final ToStringHelper stringHelper = MoreObjects.toStringHelper(this); andRun(() -> stringHelper.addValue("success")); ifFailed(e -> stringHelper.add("cause", e)); return stringHelper.toString(); } } public abstract static class TryVariableCatchVoidSuccess extends TryVariableCatchVoid { protected TryVariableCatchVoidSuccess() { /* Reducing visibility. */ } @Override public Optional getCause() { return Optional.empty(); } @Override public T map(TSupplier supplier, TFunction causeTransformation) throws Y { final T supplied = supplier.get(); checkNotNull(supplied, "Supplied null"); return supplied; } @Override public void ifFailed(TConsumer consumer) throws Y { /* Nothing to do. */ } @Override public void orThrow(Function causeTransformation) { /* Nothing to do. */ } } public abstract static class TryVariableCatchVoidFailure extends TryVariableCatchVoid { protected final X cause; protected TryVariableCatchVoidFailure(X cause) { this.cause = checkNotNull(cause); } @Override public Optional getCause() { return Optional.of(cause); } @Override public T map(TSupplier supplier, TFunction causeTransformation) throws Y { final T transformed = causeTransformation.apply(cause); checkNotNull(transformed, "Cause transformation returned null"); return transformed; } @Override public void ifFailed(TConsumer consumer) throws Y { consumer.accept(cause); } @Override public void orThrow(Function causeTransformation) throws Y { final Y transformed = causeTransformation.apply(cause); checkNotNull(transformed, "Cause transformation returned null"); throw transformed; } } public static class TrySuccess extends TryVariableCatchSuccess implements Try { public static Try given(T result) { return new TrySuccess<>(result).cast(); } private TrySuccess(T result) { super(result); } private Try cast() { /* * Safe: there is no cause in this (immutable) instance, thus its declared type does not * matter. */ @SuppressWarnings("unchecked") final Try casted = (Try) this; return casted; } @Override public Try andRun(TRunnable runnable) { final TryVoid ran = TryVoid.run(runnable); return ran.map(() -> this, Try::failure); } @Override public Try andConsume(TConsumer consumer) { return andRun(() -> consumer.accept(result)); } @Override public Try and(Try t2, TBiFunction merger) throws Y { return t2.map(u -> Try.success(merger.apply(result, u)), Try::failure); } @Override public Try andApply(TFunction mapper) { return Try.get(() -> mapper.apply(result)); } @Override public Try or( TSupplier supplier, TBiFunction exceptionsMerger) throws W { return cast(); } } public static class TryFailure extends TryOptionalImpl.TryVariableCatchFailure implements Try { public static Try given(X cause) { return new TryFailure<>(cause).cast(); } private TryFailure(X cause) { super(cause); } private Try cast() { /* * We can thus have a declared Try t which will be treated as a Try by * this class. This is fine. But do not replace with Try, otherwise, or() or * orMapCause() can attempt to produce Void instances. */ @SuppressWarnings("unchecked") final Try casted = (Try) this; return casted; } @Override public Try andRun(TRunnable runnable) { return this; } @Override public Try andConsume(TConsumer consumer) { return this; } @Override public Try and(Try t2, TBiFunction merger) throws Y { return cast(); } @Override public Try andApply(TFunction mapper) { return cast(); } @Override public Try or( TSupplier supplier, TBiFunction exceptionsMerger) throws W { final Try t2 = Try.get(supplier); return t2.map(Try::success, y -> Try.failure(exceptionsMerger.apply(cause, y))); } } public static class TryVoidSuccess extends TryVariableCatchVoidSuccess implements TryVoid { public static TryVoid given() { return new TryVoidSuccess().cast(); } private TryVoidSuccess() { /* Reducing visibility. */ } private TryVoid cast() { /* * Safe: there is no cause in this (immutable) instance, thus its declared type does not * matter. */ @SuppressWarnings("unchecked") final TryVoid casted = (TryVoid) this; return casted; } @Override public Try andGet(TSupplier supplier) { return Try.get(supplier); } @Override public TryVoid andRun(TRunnable runnable) { return TryVoid.run(runnable); } @Override public TryVoid or(TRunnable runnable) { return this; } } public static class TryVoidFailure extends TryVariableCatchVoidFailure implements TryVoid { public static TryVoid given(X cause) { return new TryVoidFailure<>(cause); } private TryVoidFailure(X cause) { super(cause); } @Override public Try andGet(TSupplier supplier) { return Try.failure(cause); } @Override public TryVoid andRun(TRunnable runnable) { return this; } @Override public TryVoid or(TRunnable runnable) { return TryVoid.run(runnable); } } public static class TryCatchAllSuccess extends TryOptionalImpl.TryVariableCatchSuccess implements TryCatchAll { public static TryCatchAll given(T result) { return new TryCatchAllSuccess<>(result); } private TryCatchAllSuccess(T result) { super(result); } @Override public TryCatchAll or(TSupplier supplier, TBiFunction exceptionsMerger) throws W { return this; } @Override public TryCatchAll andRun(TRunnable runnable) { final TryCatchAllVoid ran = TryCatchAllVoid.run(runnable); return ran.map(() -> this, TryCatchAll::failure); } @Override public TryCatchAll andConsume(TConsumer consumer) { return andRun(() -> consumer.accept(result)); } @Override public TryCatchAll and(TryCatchAll t2, TBiFunction merger) throws Y { return t2.map(u -> TryCatchAll.success(merger.apply(result, u)), TryCatchAll::failure); } @Override public TryCatchAll andApply(TFunction mapper) { return TryCatchAll.get(() -> mapper.apply(result)); } } public static class TryCatchAllFailure extends TryOptionalImpl.TryVariableCatchFailure implements TryCatchAll { public static TryCatchAll given(Throwable cause) { return new TryCatchAllFailure(cause).cast(); } private TryCatchAllFailure(Throwable cause) { super(cause); } private TryCatchAll cast() { @SuppressWarnings("unchecked") final TryCatchAll casted = (TryCatchAll) this; return casted; } @Override public TryCatchAll or(TSupplier supplier, TBiFunction exceptionsMerger) throws W { final TryCatchAll t2 = TryCatchAll.get(supplier); return t2.map(TryCatchAll::success, y -> { final Throwable merged = exceptionsMerger.apply(cause, y); checkNotNull(merged, "Exceptions merger returned null"); return TryCatchAll.failure(merged); }); } @Override public TryCatchAll andRun(TRunnable runnable) { return this; } @Override public TryCatchAll andConsume(TConsumer consumer) { return this; } @Override public TryCatchAll and(TryCatchAll t2, TBiFunction merger) throws Y { return cast(); } @Override public TryCatchAll andApply(TFunction mapper) { return cast(); } } public static class TryCatchAllVoidSuccess extends TryOptionalImpl.TryVariableCatchVoidSuccess implements TryCatchAllVoid { public static TryCatchAllVoid given() { return new TryCatchAllVoidSuccess(); } private TryCatchAllVoidSuccess() { /* Reducing visibility. */ } @Override public TryCatchAll andGet(TSupplier supplier) { return TryCatchAll.get(supplier); } @Override public TryCatchAllVoid andRun(TRunnable runnable) { return TryCatchAllVoid.run(runnable); } @Override public TryCatchAllVoid or(TRunnable runnable) { return this; } } public static class TryCatchAllVoidFailure extends TryOptionalImpl.TryVariableCatchVoidFailure implements TryCatchAllVoid { public static TryCatchAllVoid given(Throwable cause) { return new TryCatchAllVoidFailure(cause); } private TryCatchAllVoidFailure(Throwable cause) { super(cause); } @Override public TryCatchAll andGet(TSupplier supplier) { return TryCatchAll.failure(cause); } @Override public TryCatchAllVoid andRun(TRunnable runnable) { return this; } @Override public TryCatchAllVoid or(TRunnable runnable) { return TryCatchAllVoid.run(runnable); } } protected TryOptionalImpl() { /* Reducing visibility. */ } @Override public boolean isSuccess() { return getCause().isEmpty(); } @Override public boolean isFailure() { return getCause().isPresent(); } @Override public boolean equals(Object o2) { if (!(o2 instanceof TryOptional)) { return false; } final TryOptional t2 = (TryOptional) o2; return getResult().equals(t2.getResult()) && getCause().equals(t2.getCause()); } @Override public int hashCode() { return Objects.hash(getResult(), getCause()); } }