1/*
2 * Copyright (C) 2015 Google, Inc.
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 */
16package dagger.producers.internal;
17
18import com.google.common.base.Function;
19import com.google.common.collect.ImmutableSet;
20import com.google.common.util.concurrent.Futures;
21import com.google.common.util.concurrent.ListenableFuture;
22import dagger.producers.Produced;
23import dagger.producers.Producer;
24import dagger.producers.monitoring.ProducerMonitor;
25import java.util.ArrayList;
26import java.util.List;
27import java.util.Set;
28import java.util.concurrent.ExecutionException;
29
30/**
31 * A {@link Producer} implementation used to implement {@link Set} bindings. This producer returns a
32 * future {@code Set<Produced<T>>} whose elements are populated by subsequent calls to the delegate
33 * {@link Producer#get} methods.
34 *
35 * @author Jesse Beder
36 * @since 2.0
37 */
38public final class SetOfProducedProducer<T> extends AbstractProducer<Set<Produced<T>>> {
39  /**
40   * Returns a new producer that creates {@link Set} futures from the union of the given
41   * {@link Producer} instances.
42   */
43  @SafeVarargs
44  public static <T> Producer<Set<Produced<T>>> create(Producer<Set<T>>... producers) {
45    return new SetOfProducedProducer<T>(ImmutableSet.copyOf(producers));
46  }
47
48  private final ImmutableSet<Producer<Set<T>>> contributingProducers;
49
50  private SetOfProducedProducer(ImmutableSet<Producer<Set<T>>> contributingProducers) {
51    this.contributingProducers = contributingProducers;
52  }
53
54  /**
55   * Returns a future {@link Set} of {@link Produced} values whose iteration order is that of the
56   * elements given by each of the producers, which are invoked in the order given at creation.
57   *
58   * <p>If any of the delegate sets, or any elements therein, are null, then that corresponding
59   * {@code Produced} element will fail with a NullPointerException.
60   *
61   * <p>Canceling this future will attempt to cancel all of the component futures; but if any of the
62   * delegate futures fail or are canceled, this future succeeds, with the appropriate failed
63   * {@link Produced}.
64   *
65   * @throws NullPointerException if any of the delegate producers return null
66   */
67  @Override
68  public ListenableFuture<Set<Produced<T>>> compute(ProducerMonitor unusedMonitor) {
69    List<ListenableFuture<Produced<Set<T>>>> futureProducedSets =
70        new ArrayList<ListenableFuture<Produced<Set<T>>>>(contributingProducers.size());
71    for (Producer<Set<T>> producer : contributingProducers) {
72      ListenableFuture<Set<T>> futureSet = producer.get();
73      if (futureSet == null) {
74        throw new NullPointerException(producer + " returned null");
75      }
76      futureProducedSets.add(Producers.createFutureProduced(futureSet));
77    }
78    return Futures.transform(
79        Futures.allAsList(futureProducedSets),
80        new Function<List<Produced<Set<T>>>, Set<Produced<T>>>() {
81          @Override
82          public Set<Produced<T>> apply(List<Produced<Set<T>>> producedSets) {
83            ImmutableSet.Builder<Produced<T>> builder = ImmutableSet.builder();
84            for (Produced<Set<T>> producedSet : producedSets) {
85              try {
86                Set<T> set = producedSet.get();
87                if (set == null) {
88                  // TODO(beder): This is a vague exception. Can we somehow point to the failing
89                  // producer? See the similar comment in the component writer about null
90                  // provisions.
91                  builder.add(
92                      Produced.<T>failed(
93                          new NullPointerException(
94                              "Cannot contribute a null set into a producer set binding when it's"
95                                  + " injected as Set<Produced<T>>.")));
96                } else {
97                  for (T value : set) {
98                    if (value == null) {
99                      builder.add(
100                          Produced.<T>failed(
101                              new NullPointerException(
102                                  "Cannot contribute a null element into a producer set binding"
103                                      + " when it's injected as Set<Produced<T>>.")));
104                    } else {
105                      builder.add(Produced.successful(value));
106                    }
107                  }
108                }
109              } catch (ExecutionException e) {
110                builder.add(Produced.<T>failed(e.getCause()));
111              }
112            }
113            return builder.build();
114          }
115        });
116  }
117}
118