dagger.producers.internal.DependencyMethodProducer.java Source code

Java tutorial

Introduction

Here is the source code for dagger.producers.internal.DependencyMethodProducer.java

Source

/*
 * 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.
 *
 * <p>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<T> implements CancellableProducer<T> {

    /** Weak set of all incomplete futures this producer has returned. */
    private final Set<ListenableFuture<T>> futures = Collections
            .newSetFromMap(new MapMaker().weakKeys().<ListenableFuture<T>, Boolean>makeMap());

    private boolean cancelled = false;

    /** Calls a method on a component dependency to get a future. */
    protected abstract ListenableFuture<T> callDependencyMethod();

    @Override
    public final ListenableFuture<T> get() {
        synchronized (futures) {
            if (cancelled) {
                return Futures.immediateCancelledFuture();
            }

            final ListenableFuture<T> 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<T> 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<T> newDependencyView() {
        return this;
    }

    @Override
    public final Producer<T> newEntryPointView(final CancellationListener cancellationListener) {
        return new Producer<T>() {
            private final Set<ListenableFuture<T>> entryPointFutures = Collections
                    .newSetFromMap(new MapMaker().weakKeys().<ListenableFuture<T>, Boolean>makeMap());

            @Override
            public ListenableFuture<T> get() {
                final ListenableFuture<T> 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;
            }
        };
    }
}