All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dagger.producers.internal.AbstractProducer Maven / Gradle / Ivy

There is a newer version: 2.52
Show newest version
/*
 * Copyright (C) 2015 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.util.concurrent.AbstractFuture;
import com.google.common.util.concurrent.ListenableFuture;
import dagger.producers.Producer;
import java.util.concurrent.atomic.AtomicBoolean;

/** An abstract {@link Producer} implementation that memoizes the result of its compute method. */
public abstract class AbstractProducer implements CancellableProducer {
  private final AtomicBoolean requested = new AtomicBoolean();
  private final NonExternallyCancellableFuture future = new NonExternallyCancellableFuture();

  protected AbstractProducer() {}

  /** Computes this producer's future, which is then cached in {@link #get}. */
  protected abstract ListenableFuture compute();

  @Override
  public final ListenableFuture get() {
    if (requested.compareAndSet(false, true)) {
      future.setFuture(compute());
    }
    return future;
  }

  @Override
  public final void cancel(boolean mayInterruptIfRunning) {
    requested.set(true); // Avoid potentially starting the task later only to cancel it immediately.
    future.doCancel(mayInterruptIfRunning);
  }

  @Override
  public Producer newDependencyView() {
    return new NonCancellationPropagatingView();
  }

  @Override
  public Producer newEntryPointView(CancellationListener cancellationListener) {
    NonCancellationPropagatingView result = new NonCancellationPropagatingView();
    result.addCancellationListener(cancellationListener);
    return result;
  }

  /**
   * A view of this producer that returns a future that can be cancelled without cancelling the
   * producer itself.
   */
  private final class NonCancellationPropagatingView implements Producer {
    /**
     * An independently cancellable view of this node. Needs to be cancellable by normal future
     * cancellation so that the view at an entry point can listen for its cancellation.
     */
    private final ListenableFuture viewFuture = nonCancellationPropagating(future);

    @SuppressWarnings("FutureReturnValueIgnored")
    @Override
    public ListenableFuture get() {
      AbstractProducer.this.get(); // force compute()
      return viewFuture;
    }

    void addCancellationListener(final CancellationListener cancellationListener) {
      viewFuture.addListener(
          new Runnable() {
            @Override
            public void run() {
              if (viewFuture.isCancelled()) {
                boolean mayInterruptIfRunning =
                    viewFuture instanceof NonCancellationPropagatingFuture
                        && ((NonCancellationPropagatingFuture) viewFuture).interrupted();
                cancellationListener.onProducerFutureCancelled(mayInterruptIfRunning);
              }
            }
          },
          directExecutor());
    }
  }

  /** A settable future that can't be cancelled via normal future cancellation. */
  private static final class NonExternallyCancellableFuture extends AbstractFuture {

    @Override
    public boolean setFuture(ListenableFuture future) {
      return super.setFuture(future);
    }

    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
      return false;
    }

    /** Actually cancels this future. */
    void doCancel(boolean mayInterruptIfRunning) {
      super.cancel(mayInterruptIfRunning);
    }
  }

  private static  ListenableFuture nonCancellationPropagating(ListenableFuture future) {
    if (future.isDone()) {
      return future;
    }
    NonCancellationPropagatingFuture output = new NonCancellationPropagatingFuture(future);
    future.addListener(output, directExecutor());
    return output;
  }

  /**
   * Equivalent to {@code Futures.nonCancellationPropagating}, but allowing us to check whether or
   * not {@code mayInterruptIfRunning} was set when cancelling it.
   */
  private static final class NonCancellationPropagatingFuture extends AbstractFuture
      implements Runnable {
    // TODO(cgdecker): This is copied directly from Producers.nonCancellationPropagating, but try
    // to find out why this doesn't need to be volatile.
    private ListenableFuture delegate;

    NonCancellationPropagatingFuture(final ListenableFuture delegate) {
      this.delegate = delegate;
    }

    @Override
    public void run() {
      // This prevents cancellation from propagating because we don't call setFuture(delegate) until
      // delegate is already done, so calling cancel() on this future won't affect it.
      ListenableFuture localDelegate = delegate;
      if (localDelegate != null) {
        setFuture(localDelegate);
      }
    }

    @Override
    protected String pendingToString() {
      ListenableFuture localDelegate = delegate;
      if (localDelegate != null) {
        return "delegate=[" + localDelegate + "]";
      }
      return null;
    }

    @Override
    protected void afterDone() {
      delegate = null;
    }

    public boolean interrupted() {
      return super.wasInterrupted();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy