/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* 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.
*/
package com.google.android.agera;
import static com.google.android.agera.Preconditions.checkArgument;
import static com.google.android.agera.Preconditions.checkNotNull;
import static com.google.android.agera.Preconditions.checkState;
import static java.util.Collections.singletonList;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.Collections;
import java.util.List;
/**
* An immutable object encapsulating the result of an <i>attempt</i>. An attempt is a call to
* {@link Function#apply}, {@link Merger#merge} or {@link Supplier#get} that may fail. This class
* helps avoid throwing exceptions from those methods, by encapsulating either the output value of
* those calls, or the failure encountered. In this way, an attempt always produces a {@link Result}
* whether it has {@link #succeeded} or {@link #failed}.
*
* <p>This class can also be used to wrap a nullable value for situations where the value is indeed
* null, but null is not accepted. In this case a {@link Result} instance representing a failed
* attempt to obtain a non-null value can be used in place of the nullable value.
*
* @param <T> The output value type.
*/
public final class Result<T> {
@NonNull
private static final Result<Object> ABSENT;
@NonNull
private static final Result<Object> FAILURE;
@NonNull
private static final Throwable ABSENT_THROWABLE;
static {
final Throwable failureThrowable = new Throwable("Attempt failed");
failureThrowable.setStackTrace(new StackTraceElement[0]);
FAILURE = new Result<>(null, failureThrowable);
ABSENT_THROWABLE = new NullPointerException("Value is absent");
ABSENT_THROWABLE.setStackTrace(new StackTraceElement[0]);
ABSENT = new Result<>(null, ABSENT_THROWABLE);
}
@Nullable
private final T value;
@Nullable
private transient volatile List<T> list;
@Nullable
private final Throwable failure;
Result(@Nullable final T value, @Nullable final Throwable failure) {
checkArgument(value != null ^ failure != null, "Illegal Result arguments");
this.value = value;
this.failure = failure;
this.list = value != null ? null : Collections.<T>emptyList();
}
/**
* Creates a {@link Result} of a successful attempt that produced the given {@code value}.
*/
@NonNull
public static <T> Result<T> success(@NonNull final T value) {
return new Result<>(checkNotNull(value), null);
}
/**
* Creates a {@link Result} of a failed attempt that encountered the given {@code failure}.
*/
@NonNull
public static <T> Result<T> failure(@NonNull final Throwable failure) {
return failure == ABSENT_THROWABLE
? Result.<T>absent() : new Result<T>(null, checkNotNull(failure));
}
/**
* Returns the singleton {@link Result} denoting a failed attempt that has a generic failure.
*/
@SuppressWarnings("unchecked")
@NonNull
public static <T> Result<T> failure() {
return (Result<T>) FAILURE;
}
/**
* Creates a {@link Result} denoting a non-null value. This is an alias of {@link #success}.
*/
@NonNull
public static <T> Result<T> present(@NonNull final T value) {
return success(value);
}
/**
* Returns the singleton {@link Result} denoting an absent value, with a failure of
* {@link NullPointerException}.
*/
@SuppressWarnings("unchecked")
@NonNull
public static <T> Result<T> absent() {
return (Result<T>) ABSENT;
}
/**
* Creates a {@code Result} denoting the {@code value} if it is non-null, or returns the singleton
* {@link #absent} result.
*/
@NonNull
public static <T> Result<T> absentIfNull(@Nullable final T value) {
return value == null ? Result.<T>absent() : present(value);
}
/**
* Returns whether this is the result of a successful attempt.
*/
public boolean succeeded() {
return value != null;
}
/**
* Returns whether this is the result of a failed attempt.
*/
public boolean failed() {
return value == null;
}
/**
* Returns whether the output value is present. This is an alias of {@link #succeeded()}.
*/
public boolean isPresent() {
return succeeded();
}
/**
* Returns whether this is a result denoting an absent value. This is <i>not</i> an alias of
* {@link #failed()}; this checks whether this instance is obtained from {@link #absent()}.
*/
public boolean isAbsent() {
return this == ABSENT;
}
/**
* Returns the output value of the successful attempt that produced this result.
*
* @throws FailedResultException If this is the result of a {@link #failed} attempt. The failure
* is available from {@link FailedResultException#getCause()}. This is an unchecked exception
* because it is easily avoidable by first checking whether the attempt has {@link #succeeded}
* or {@link #failed}, or by using other fluent style methods to achieve the same purpose.
*/
@NonNull
public T get() throws FailedResultException {
if (value != null) {
return value;
}
throw new FailedResultException(failure);
}
/**
* Returns a list containing the value if it is present, or an empty list.
*/
@NonNull
public List<T> asList() {
List<T> list = this.list;
if (list == null) {
synchronized (this) {
list = this.list;
if (list == null) {
this.list = list = singletonList(value);
}
}
}
return list;
}
/**
* Returns the failure encountered in the attempt that produced this result.
*
* @throws IllegalStateException If this is the result of a {@link #succeeded} attempt.
*/
@NonNull
public Throwable getFailure() {
checkState(failure != null, "Not a failure");
return failure;
}
/**
* Returns the output value of the successful attempt, or null if the attempt has {@link #failed}.
*/
@Nullable
public T orNull() {
return value;
}
/**
* Returns the failure encountered in the attempt that produced this result, or null if the
* attempt has {@link #succeeded}.
*/
@Nullable
public Throwable failureOrNull() {
return failure;
}
/**
* Passes the output value to the {@code receiver} if the attempt has succeeded; otherwise does
* nothing.
*
* @return This instance, for chaining.
*/
@NonNull
public Result<T> ifSucceededSendTo(@NonNull final Receiver<? super T> receiver) {
if (value != null) {
receiver.accept(value);
}
return this;
}
/**
* Passes the encountered failure to the {@code receiver} if the attempt has failed; otherwise
* does nothing.
*
* @return This instance, for chaining.
*/
@NonNull
public Result<T> ifFailedSendTo(@NonNull final Receiver<? super Throwable> receiver) {
if (failure != null) {
receiver.accept(failure);
}
return this;
}
/**
* Passes the encountered failure to the {@code receiver} if the failure is absent; otherwise
* does nothing.
*
* @return This instance, for chaining.
*/
@NonNull
public Result<T> ifAbsentFailureSendTo(@NonNull final Receiver<? super Throwable> receiver) {
if (failure == ABSENT_THROWABLE) {
receiver.accept(failure);
}
return this;
}
/**
* Passes the encountered failure to the {@code receiver} if the attempt has failed, except for
* the failure absent; otherwise does nothing.
*
* @return This instance, for chaining.
*/
@NonNull
public Result<T> ifNonAbsentFailureSendTo(@NonNull final Receiver<? super Throwable> receiver) {
if (failure != null && failure != ABSENT_THROWABLE) {
receiver.accept(failure);
}
return this;
}
/**
* Binds the output value with {@code bindValue} using {@code binder} if the attempt has
* succeeded; otherwise does nothing.
*
* @return This instance, for chaining.
*/
@NonNull
public <U> Result<T> ifSucceededBind(@NonNull final U bindValue,
@NonNull final Binder<? super T, ? super U> binder) {
if (value != null) {
binder.bind(value, bindValue);
}
return this;
}
/**
* Binds the output value with {@code bindValue} using {@code binder} if the attempt has failed;
* otherwise does nothing.
*
* @return This instance, for chaining.
*/
@NonNull
public <U> Result<T> ifFailedBind(@NonNull final U bindValue,
@NonNull final Binder<Throwable, ? super U> binder) {
if (failure != null) {
binder.bind(failure, bindValue);
}
return this;
}
/**
* Binds the output value with {@code bindValue} using {@code binder} if the failure is absent;
* otherwise does nothing.
*
* @return This instance, for chaining.
*/
@NonNull
public <U> Result<T> ifAbsentFailureBind(@NonNull final U bindValue,
@NonNull final Binder<Throwable, ? super U> binder) {
if (failure == ABSENT_THROWABLE) {
binder.bind(failure, bindValue);
}
return this;
}
/**
* Binds the output value with {@code bindValue} using {@code binder} if the attempt has failed,
* except for the failure absent; otherwise does nothing.
*
* @return This instance, for chaining.
*/
@NonNull
public <U> Result<T> ifNonAbsentFailureBind(@NonNull final U bindValue,
@NonNull final Binder<Throwable, ? super U> binder) {
if (failure != null && failure != ABSENT_THROWABLE) {
binder.bind(failure, bindValue);
}
return this;
}
/**
* Binds the output value with the value from the {@code supplier} using {@code binder} if the
* attempt has succeeded; otherwise does nothing, not calling either the binder or the supplier.
*
* @return This instance, for chaining.
*/
@NonNull
public <U> Result<T> ifSucceededBindFrom(@NonNull final Supplier<U> supplier,
@NonNull final Binder<? super T, ? super U> binder) {
if (value != null) {
binder.bind(value, supplier.get());
}
return this;
}
/**
* Binds the output value with the value from the {@code supplier} using {@code binder} if the
* attempt has failed; otherwise does nothing.
*
* @return This instance, for chaining.
*/
@NonNull
public <U> Result<T> ifFailedBindFrom(@NonNull final Supplier<U> supplier,
@NonNull final Binder<Throwable, ? super U> binder) {
if (failure != null) {
binder.bind(failure, supplier.get());
}
return this;
}
/**
* Binds the output value with the value from the {@code supplier} using {@code binder} if the
* failure is absent; otherwise does nothing.
*
* @return This instance, for chaining.
*/
@NonNull
public <U> Result<T> ifAbsentFailureBindFrom(@NonNull final Supplier<U> supplier,
@NonNull final Binder<Throwable, ? super U> binder) {
if (failure == ABSENT_THROWABLE) {
binder.bind(failure, supplier.get());
}
return this;
}
/**
* Binds the output value with the value from the {@code supplier} using {@code binder} if the
* attempt has failed, except for the failure absent; otherwise does nothing.
*
* @return This instance, for chaining.
*/
@NonNull
public <U> Result<T> ifNonAbsentFailureBindFrom(@NonNull final Supplier<U> supplier,
@NonNull final Binder<Throwable, ? super U> binder) {
if (failure != null && failure != ABSENT_THROWABLE) {
binder.bind(failure, supplier.get());
}
return this;
}
/**
* Returns a result denoting a failed attempt to obtain a value of a different type, with the same
* failure.
*
* @throws IllegalStateException If this is the result of a {@link #succeeded} attempt.
*/
@SuppressWarnings("unchecked")
@NonNull
public <U> Result<U> sameFailure() {
checkState(failed(), "Not a failure");
return (Result<U>) this;
}
/**
* Returns a {@link Result} wrapping the result of applying the given {@code function} to the
* output value encapsulated in this result, or if this is the result of a {@link #failed}
* attempt, returns the {@link #sameFailure}.
*/
@NonNull
public <U> Result<U> ifSucceededMap(@NonNull final Function<? super T, U> function) {
if (value != null) {
return success(function.apply(value));
}
return sameFailure();
}
/**
* Returns the result of a follow-up attempt to apply the given {@code attemptFunction} to the
* output value encapsulated in this result, or if this is the result of a {@link #failed}
* attempt, returns the {@link #sameFailure}.
*/
@NonNull
public <U> Result<U> ifSucceededAttemptMap(
@NonNull final Function<? super T, Result<U>> attemptFunction) {
if (value != null) {
return attemptFunction.apply(value);
}
return sameFailure();
}
/**
* Returns a {@link Result} wrapping the result of merging the output value encapsulated in this
* result with the given {@code mergeValue} using the {@code merger}, or if this is the result of
* a {@link #failed} attempt, returns the {@link #sameFailure}.
*/
@NonNull
public <U, V> Result<V> ifSucceededMerge(@NonNull final U mergeValue,
@NonNull final Merger<? super T, ? super U, V> merger) {
if (value != null) {
return success(merger.merge(value, mergeValue));
}
return sameFailure();
}
/**
* Returns the result of a follow-up attempt to merge the output value encapsulated in this
* result with the given {@code mergeValue} using the {@code attemptMerger}, or if this is the
* result of a {@link #failed} attempt, returns the {@link #sameFailure}.
*/
@NonNull
public <U, V> Result<V> ifSucceededAttemptMerge(@NonNull final U mergeValue,
@NonNull final Merger<? super T, ? super U, Result<V>> attemptMerger) {
if (value != null) {
return attemptMerger.merge(value, mergeValue);
}
return sameFailure();
}
/**
* Returns a {@link Result} wrapping the result of merging the output value encapsulated in this
* result with the value from the given {@code mergeValueSupplier} using the {@code merger}, or if
* this is the result of a {@link #failed} attempt, returns the {@link #sameFailure}.
*/
@NonNull
public <U, V> Result<V> ifSucceededMergeFrom(@NonNull final Supplier<U> mergeValueSupplier,
@NonNull final Merger<? super T, ? super U, V> merger) {
if (value != null) {
return success(merger.merge(value, mergeValueSupplier.get()));
}
return sameFailure();
}
/**
* Returns the result of a follow-up attempt to merge the output value encapsulated in this
* result with the value from the given {@code mergeValueSupplier} using the
* {@code attemptMerger}, or if this is the result of a {@link #failed} attempt, returns the
* {@link #sameFailure}.
*
* <p>This method is agnostic of the value type of the {@code mergeValueSupplier}. If it is also
* fallible, the {@code attemptMerger} has the responsibility to interpret the result should the
* supplier fail. The merger may choose to, for example, return the {@code sameFailure()} if this
* happens.
*/
@NonNull
public <U, V> Result<V> ifSucceededAttemptMergeFrom(
@NonNull final Supplier<U> mergeValueSupplier,
@NonNull final Merger<? super T, ? super U, Result<V>> attemptMerger) {
if (value != null) {
return attemptMerger.merge(value, mergeValueSupplier.get());
}
return sameFailure();
}
/**
* Returns the output value if the attempt has succeeded, or the given {@code other} value
* otherwise.
*/
@NonNull
public T orElse(@NonNull final T other) {
return value != null ? value : checkNotNull(other);
}
/**
* Returns the output value if the attempt has succeeded, or the value from the given
* {@code supplier} otherwise.
*/
@NonNull
public T orGetFrom(@NonNull final Supplier<? extends T> supplier) {
return value != null ? value : Preconditions.<T>checkNotNull(supplier.get());
}
/**
* Returns the same result if the attempt has succeeded, or the result of the attempt to get from
* the given {@code attemptSupplier} otherwise.
*/
@SuppressWarnings("unchecked")
@NonNull
public Result<T> orAttemptGetFrom(
@NonNull final Supplier<? extends Result<? extends T>> supplier) {
return value != null ? this : (Result<T>) checkNotNull(supplier.get());
}
/**
* Returns the output value if the attempt has succeeded, or the resulting value of applying the
* given {@code recoverFunction} to the failure of the attempt.
*/
@SuppressWarnings("ConstantConditions")
@NonNull
public T recover(@NonNull final Function<? super Throwable, ? extends T> recoverFunction) {
if (value != null) {
return value;
}
return recoverFunction.apply(failure);
}
/**
* Returns the same result if the attempt has succeeded, or the result of the attempt to apply the
* given {@code attemptRecoverFunction} to the failure of the attempt.
*/
@SuppressWarnings({"ConstantConditions", "unchecked"})
@NonNull
public Result<T> attemptRecover(
@NonNull final Function<? super Throwable, ? extends Result<? extends T>>
attemptRecoverFunction) {
if (value != null) {
return this;
}
return (Result<T>) attemptRecoverFunction.apply(failure);
}
public boolean contains(@NonNull final T value) {
return this.value != null && this.value.equals(value);
}
@Override
public boolean equals(final Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
final Result<?> result = (Result<?>) o;
if (value != null ? !value.equals(result.value) : result.value != null) { return false; }
if (failure != null ? !failure.equals(result.failure) : result.failure != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = value != null ? value.hashCode() : 0;
result = 31 * result + (failure != null ? failure.hashCode() : 0);
return result;
}
@Override
public String toString() {
if (this == ABSENT) {
return "Result{Absent}";
}
if (this == FAILURE) {
return "Result{Failure}";
}
if (value != null) {
return "Result{Success; value=" + value + "}";
}
return "Result{Failure; failure=" + failure + "}";
}
}