Java tutorial
/* * Copyright (C) 2018 The Dagger Authors. * * 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 dagger.producers.internal; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import com.google.common.collect.MapMaker; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import dagger.producers.Producer; import java.util.Collections; import java.util.Set; /** * Abstract class for implementing producers derived from methods on component dependencies. * * <p>Unlike most other {@link CancellableProducer} implementations, cancelling the future returned * by a {@linkplain #newDependencyView dependency view} injected into an {@code @Produces} method * will actually cancel the underlying future. This is because the future comes from outside the * component's producer graph (including possibly from another object that isn't a component at * all), so if we don't cancel it when the user asks to cancel it, there might just be no way to * cancel it at all. */ public abstract class DependencyMethodProducer<T> implements CancellableProducer<T> { /** Weak set of all incomplete futures this producer has returned. */ private final Set<ListenableFuture<T>> futures = Collections .newSetFromMap(new MapMaker().weakKeys().<ListenableFuture<T>, Boolean>makeMap()); private boolean cancelled = false; /** Calls a method on a component dependency to get a future. */ protected abstract ListenableFuture<T> callDependencyMethod(); @Override public final ListenableFuture<T> get() { synchronized (futures) { if (cancelled) { return Futures.immediateCancelledFuture(); } final ListenableFuture<T> future = callDependencyMethod(); if (!future.isDone() && futures.add(future)) { future.addListener(new Runnable() { @Override public void run() { synchronized (futures) { futures.remove(future); } } }, directExecutor()); } return future; } } @Override public final void cancel(boolean mayInterruptIfRunning) { synchronized (futures) { cancelled = true; for (ListenableFuture<T> future : futures) { // futures is a concurrent set so that the concurrent removal that will happen here is not // a problem future.cancel(mayInterruptIfRunning); } } } @Override public final Producer<T> newDependencyView() { return this; } @Override public final Producer<T> newEntryPointView(final CancellationListener cancellationListener) { return new Producer<T>() { private final Set<ListenableFuture<T>> entryPointFutures = Collections .newSetFromMap(new MapMaker().weakKeys().<ListenableFuture<T>, Boolean>makeMap()); @Override public ListenableFuture<T> get() { final ListenableFuture<T> future = DependencyMethodProducer.this.get(); if (!future.isDone() && entryPointFutures.add(future)) { future.addListener(new Runnable() { @Override public void run() { entryPointFutures.remove(future); if (future.isCancelled()) { // TODO(cgdecker): Make this also propagate the actual value that was passed for // mayInterruptIfRunning cancellationListener.onProducerFutureCancelled(true); } } }, directExecutor()); } return future; } }; } }