blob: 397549d4e809097157c48a1b26281f8aa82bbadf [file] [log] [blame]
/*
* Copyright 2022 The Android Open Source Project
*
* 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 androidx.camera.previewview.internal.utils.futures;
import static androidx.core.util.Preconditions.checkNotNull;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.arch.core.util.Function;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.core.util.Preconditions;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* A {@link ListenableFuture} that supports chains of operations. For example:
*
* <pre>{@code
* ListenableFuture<Boolean> adminIsLoggedIn =
* FutureChain.from(usersDatabase.getAdminUser())
* .transform(User::getId, directExecutor())
* .transform(ActivityService::isLoggedIn, threadPool);
* }</pre>
* @param <V>
*
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public class FutureChain<V> implements ListenableFuture<V> {
@NonNull
private final ListenableFuture<V> mDelegate;
@Nullable
CallbackToFutureAdapter.Completer<V> mCompleter;
/**
* Converts the given {@code ListenableFuture} to an equivalent {@code FutureChain}.
*
* <p>If the given {@code ListenableFuture} is already a {@code FutureChain}, it is returned
* directly. If not, it is wrapped in a {@code FutureChain} that delegates all calls to the
* original {@code ListenableFuture}.
*
* @return directly if input a FutureChain or a ListenableFuture wrapped by FutureChain.
*/
@NonNull
public static <V> FutureChain<V> from(@NonNull ListenableFuture<V> future) {
return future instanceof FutureChain
? (FutureChain<V>) future : new FutureChain<V>(future);
}
/**
* Returns a new {@code Future} whose result is derived from the result of this {@code
* Future}.
* If this input {@code Future} fails, the returned {@code Future} fails with the same
* exception (and the function is not invoked).
*
* @param function A Function to transform the results of this future to the results of the
* returned future.
* @param executor Executor to run the function in.
* @return A future that holds result of the transformation.
*/
@NonNull
public final <T> FutureChain<T> transform(@NonNull Function<? super V, T> function,
@NonNull Executor executor) {
return (FutureChain<T>) Futures.transform(this, function, executor);
}
FutureChain(@NonNull ListenableFuture<V> delegate) {
mDelegate = checkNotNull(delegate);
}
FutureChain() {
mDelegate = CallbackToFutureAdapter.getFuture(
new CallbackToFutureAdapter.Resolver<V>() {
@Override
public Object attachCompleter(
@NonNull CallbackToFutureAdapter.Completer<V> completer) {
Preconditions.checkState(mCompleter == null,
"The result can only set once!");
mCompleter = completer;
return "FutureChain[" + FutureChain.this + "]";
}
});
}
@Override
public void addListener(@NonNull Runnable listener, @NonNull Executor executor) {
mDelegate.addListener(listener, executor);
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return mDelegate.cancel(mayInterruptIfRunning);
}
@Override
public boolean isCancelled() {
return mDelegate.isCancelled();
}
@Override
public boolean isDone() {
return mDelegate.isDone();
}
@Nullable
@Override
public V get() throws InterruptedException, ExecutionException {
return mDelegate.get();
}
@Nullable
@Override
public V get(long timeout, @NonNull TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return mDelegate.get(timeout, unit);
}
boolean set(@Nullable V value) {
if (mCompleter != null) {
return mCompleter.set(value);
}
return false;
}
boolean setException(@NonNull Throwable throwable) {
if (mCompleter != null) {
return mCompleter.setException(throwable);
}
return false;
}
}