/* * 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. * *

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 implements CancellableProducer { /** Weak set of all incomplete futures this producer has returned. */ private final Set> futures = Collections.newSetFromMap(new MapMaker().weakKeys()., Boolean>makeMap()); private boolean cancelled = false; /** Calls a method on a component dependency to get a future. */ protected abstract ListenableFuture callDependencyMethod(); @Override public final ListenableFuture get() { synchronized (futures) { if (cancelled) { return Futures.immediateCancelledFuture(); } final ListenableFuture 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 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 newDependencyView() { return this; } @Override public final Producer newEntryPointView(final CancellationListener cancellationListener) { return new Producer() { private final Set> entryPointFutures = Collections.newSetFromMap( new MapMaker().weakKeys()., Boolean>makeMap()); @Override public ListenableFuture get() { final ListenableFuture 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; } }; } }