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