• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.util.concurrent.AbstractFuture;
22 import com.google.common.util.concurrent.ListenableFuture;
23 import dagger.producers.Producer;
24 import java.util.concurrent.atomic.AtomicBoolean;
25 
26 /** An abstract {@link Producer} implementation that memoizes the result of its compute method. */
27 public abstract class AbstractProducer<T> implements CancellableProducer<T> {
28   private final AtomicBoolean requested = new AtomicBoolean();
29   private final NonExternallyCancellableFuture<T> future = new NonExternallyCancellableFuture<T>();
30 
AbstractProducer()31   protected AbstractProducer() {}
32 
33   /** Computes this producer's future, which is then cached in {@link #get}. */
compute()34   protected abstract ListenableFuture<T> compute();
35 
36   @Override
get()37   public final ListenableFuture<T> get() {
38     if (requested.compareAndSet(false, true)) {
39       future.setFuture(compute());
40     }
41     return future;
42   }
43 
44   @Override
cancel(boolean mayInterruptIfRunning)45   public final void cancel(boolean mayInterruptIfRunning) {
46     requested.set(true); // Avoid potentially starting the task later only to cancel it immediately.
47     future.doCancel(mayInterruptIfRunning);
48   }
49 
50   @Override
newDependencyView()51   public Producer<T> newDependencyView() {
52     return new NonCancellationPropagatingView();
53   }
54 
55   @Override
newEntryPointView(CancellationListener cancellationListener)56   public Producer<T> newEntryPointView(CancellationListener cancellationListener) {
57     NonCancellationPropagatingView result = new NonCancellationPropagatingView();
58     result.addCancellationListener(cancellationListener);
59     return result;
60   }
61 
62   /**
63    * A view of this producer that returns a future that can be cancelled without cancelling the
64    * producer itself.
65    */
66   private final class NonCancellationPropagatingView implements Producer<T> {
67     /**
68      * An independently cancellable view of this node. Needs to be cancellable by normal future
69      * cancellation so that the view at an entry point can listen for its cancellation.
70      */
71     private final ListenableFuture<T> viewFuture = nonCancellationPropagating(future);
72 
73     @SuppressWarnings("FutureReturnValueIgnored")
74     @Override
get()75     public ListenableFuture<T> get() {
76       AbstractProducer.this.get(); // force compute()
77       return viewFuture;
78     }
79 
addCancellationListener(final CancellationListener cancellationListener)80     void addCancellationListener(final CancellationListener cancellationListener) {
81       viewFuture.addListener(
82           new Runnable() {
83             @Override
84             public void run() {
85               if (viewFuture.isCancelled()) {
86                 boolean mayInterruptIfRunning =
87                     viewFuture instanceof NonCancellationPropagatingFuture
88                         && ((NonCancellationPropagatingFuture) viewFuture).interrupted();
89                 cancellationListener.onProducerFutureCancelled(mayInterruptIfRunning);
90               }
91             }
92           },
93           directExecutor());
94     }
95   }
96 
97   /** A settable future that can't be cancelled via normal future cancellation. */
98   private static final class NonExternallyCancellableFuture<T> extends AbstractFuture<T> {
99 
100     @Override
setFuture(ListenableFuture<? extends T> future)101     public boolean setFuture(ListenableFuture<? extends T> future) {
102       return super.setFuture(future);
103     }
104 
105     @Override
cancel(boolean mayInterruptIfRunning)106     public boolean cancel(boolean mayInterruptIfRunning) {
107       return false;
108     }
109 
110     /** Actually cancels this future. */
doCancel(boolean mayInterruptIfRunning)111     void doCancel(boolean mayInterruptIfRunning) {
112       super.cancel(mayInterruptIfRunning);
113     }
114   }
115 
nonCancellationPropagating(ListenableFuture<T> future)116   private static <T> ListenableFuture<T> nonCancellationPropagating(ListenableFuture<T> future) {
117     if (future.isDone()) {
118       return future;
119     }
120     NonCancellationPropagatingFuture<T> output = new NonCancellationPropagatingFuture<T>(future);
121     future.addListener(output, directExecutor());
122     return output;
123   }
124 
125   /**
126    * Equivalent to {@code Futures.nonCancellationPropagating}, but allowing us to check whether or
127    * not {@code mayInterruptIfRunning} was set when cancelling it.
128    */
129   private static final class NonCancellationPropagatingFuture<T> extends AbstractFuture<T>
130       implements Runnable {
131     // TODO(cgdecker): This is copied directly from Producers.nonCancellationPropagating, but try
132     // to find out why this doesn't need to be volatile.
133     private ListenableFuture<T> delegate;
134 
NonCancellationPropagatingFuture(final ListenableFuture<T> delegate)135     NonCancellationPropagatingFuture(final ListenableFuture<T> delegate) {
136       this.delegate = delegate;
137     }
138 
139     @Override
run()140     public void run() {
141       // This prevents cancellation from propagating because we don't call setFuture(delegate) until
142       // delegate is already done, so calling cancel() on this future won't affect it.
143       ListenableFuture<T> localDelegate = delegate;
144       if (localDelegate != null) {
145         setFuture(localDelegate);
146       }
147     }
148 
149     @Override
pendingToString()150     protected String pendingToString() {
151       ListenableFuture<T> localDelegate = delegate;
152       if (localDelegate != null) {
153         return "delegate=[" + localDelegate + "]";
154       }
155       return null;
156     }
157 
158     @Override
afterDone()159     protected void afterDone() {
160       delegate = null;
161     }
162 
interrupted()163     public boolean interrupted() {
164       return super.wasInterrupted();
165     }
166   }
167 }
168