• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.producers.internal;
18 
19 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
20 
21 import com.google.common.collect.MapMaker;
22 import com.google.common.util.concurrent.Futures;
23 import com.google.common.util.concurrent.ListenableFuture;
24 import dagger.producers.Producer;
25 import java.util.Collections;
26 import java.util.Set;
27 
28 /**
29  * Abstract class for implementing producers derived from methods on component dependencies.
30  *
31  * <p>Unlike most other {@link CancellableProducer} implementations, cancelling the future returned
32  * by a {@linkplain #newDependencyView dependency view} injected into an {@code @Produces} method
33  * will actually cancel the underlying future. This is because the future comes from outside the
34  * component's producer graph (including possibly from another object that isn't a component at
35  * all), so if we don't cancel it when the user asks to cancel it, there might just be no way to
36  * cancel it at all.
37  */
38 public abstract class DependencyMethodProducer<T> implements CancellableProducer<T> {
39 
40   /** Weak set of all incomplete futures this producer has returned. */
41   private final Set<ListenableFuture<T>> futures =
42       Collections.newSetFromMap(new MapMaker().weakKeys().<ListenableFuture<T>, Boolean>makeMap());
43 
44   private boolean cancelled = false;
45 
46   /** Calls a method on a component dependency to get a future. */
callDependencyMethod()47   protected abstract ListenableFuture<T> callDependencyMethod();
48 
49   @Override
get()50   public final ListenableFuture<T> get() {
51     synchronized (futures) {
52       if (cancelled) {
53         return Futures.immediateCancelledFuture();
54       }
55 
56       final ListenableFuture<T> future = callDependencyMethod();
57       if (!future.isDone() && futures.add(future)) {
58         future.addListener(
59             new Runnable() {
60               @Override
61               public void run() {
62                 synchronized (futures) {
63                   futures.remove(future);
64                 }
65               }
66             },
67             directExecutor());
68       }
69       return future;
70     }
71   }
72 
73   @Override
cancel(boolean mayInterruptIfRunning)74   public final void cancel(boolean mayInterruptIfRunning) {
75     synchronized (futures) {
76       cancelled = true;
77       for (ListenableFuture<T> future : futures) {
78         // futures is a concurrent set so that the concurrent removal that will happen here is not
79         // a problem
80         future.cancel(mayInterruptIfRunning);
81       }
82     }
83   }
84 
85   @Override
newDependencyView()86   public final Producer<T> newDependencyView() {
87     return this;
88   }
89 
90   @Override
newEntryPointView(final CancellationListener cancellationListener)91   public final Producer<T> newEntryPointView(final CancellationListener cancellationListener) {
92     return new Producer<T>() {
93       private final Set<ListenableFuture<T>> entryPointFutures =
94           Collections.newSetFromMap(
95               new MapMaker().weakKeys().<ListenableFuture<T>, Boolean>makeMap());
96 
97       @Override
98       public ListenableFuture<T> get() {
99         final ListenableFuture<T> future = DependencyMethodProducer.this.get();
100         if (!future.isDone() && entryPointFutures.add(future)) {
101           future.addListener(
102               new Runnable() {
103                 @Override
104                 public void run() {
105                   entryPointFutures.remove(future);
106                   if (future.isCancelled()) {
107                     // TODO(cgdecker): Make this also propagate the actual value that was passed for
108                     // mayInterruptIfRunning
109                     cancellationListener.onProducerFutureCancelled(true);
110                   }
111                 }
112               },
113               directExecutor());
114         }
115         return future;
116       }
117     };
118   }
119 }
120