1/*
2 * Copyright (C) 2014 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.internal.codegen;
17
18import com.google.auto.common.MoreTypes;
19import com.google.common.base.Equivalence;
20import com.google.common.base.Equivalence.Wrapper;
21import com.google.common.base.Function;
22import com.google.common.base.Optional;
23import com.google.common.base.Predicate;
24import com.google.common.collect.ImmutableListMultimap;
25import com.google.common.collect.ImmutableSetMultimap;
26import com.google.common.collect.Iterables;
27import com.google.common.collect.Multimaps;
28import com.google.common.collect.Ordering;
29import com.google.common.collect.Sets;
30import com.google.common.util.concurrent.ListenableFuture;
31import dagger.Component;
32import dagger.MapKey;
33import dagger.Provides;
34import dagger.producers.Produces;
35import dagger.producers.ProductionComponent;
36import java.util.EnumSet;
37import java.util.Set;
38import javax.inject.Inject;
39import javax.lang.model.element.AnnotationMirror;
40import javax.lang.model.element.AnnotationValue;
41import javax.lang.model.element.TypeElement;
42import javax.lang.model.type.DeclaredType;
43
44import static com.google.common.base.Preconditions.checkArgument;
45import static com.google.common.base.Preconditions.checkNotNull;
46import static dagger.internal.codegen.MapKeys.getMapKey;
47import static dagger.internal.codegen.MapKeys.unwrapValue;
48import static javax.lang.model.element.Modifier.STATIC;
49
50/**
51 * An abstract class for a value object representing the mechanism by which a {@link Key} can be
52 * contributed to a dependency graph.
53 *
54 * @author Jesse Beder
55 * @since 2.0
56 */
57abstract class ContributionBinding extends Binding {
58
59  @Override
60  Set<DependencyRequest> implicitDependencies() {
61    // Optimization: If we don't need the memberInjectionRequest, don't create more objects.
62    if (!membersInjectionRequest().isPresent()) {
63      return dependencies();
64    } else {
65      // Optimization: Avoid creating an ImmutableSet+Builder just to union two things together.
66      return Sets.union(membersInjectionRequest().asSet(), dependencies());
67    }
68  }
69
70  static enum ContributionType {
71    /** Represents map bindings. */
72    MAP,
73    /** Represents set bindings. */
74    SET,
75    /** Represents a valid non-collection binding. */
76    UNIQUE;
77
78    boolean isMultibinding() {
79      return !this.equals(UNIQUE);
80    }
81  }
82
83  ContributionType contributionType() {
84    switch (provisionType()) {
85      case SET:
86      case SET_VALUES:
87        return ContributionType.SET;
88      case MAP:
89        return ContributionType.MAP;
90      case UNIQUE:
91        return ContributionType.UNIQUE;
92      default:
93        throw new AssertionError("Unknown provision type: " + provisionType());
94    }
95  }
96
97  /** Returns the type that specifies this' nullability, absent if not nullable. */
98  abstract Optional<DeclaredType> nullableType();
99
100  /**
101   * If this is a provision request from an {@code @Provides} or {@code @Produces} method, this will
102   * be the element that contributed it. In the case of subclassed modules, this may differ than the
103   * binding's enclosed element, as this will return the subclass whereas the enclosed element will
104   * be the superclass.
105   */
106  abstract Optional<TypeElement> contributedBy();
107
108  /**
109   * Returns whether this binding is synthetic, i.e., not explicitly tied to code, but generated
110   * implicitly by the framework.
111   */
112  boolean isSyntheticBinding() {
113    return bindingKind().equals(Kind.SYNTHETIC);
114  }
115
116  /** If this provision requires members injection, this will be the corresponding request. */
117  abstract Optional<DependencyRequest> membersInjectionRequest();
118
119  /**
120   * The kind of contribution this binding represents. Defines which elements can specify this kind
121   * of contribution.
122   */
123  enum Kind {
124    /**
125     * A binding that is not explicitly tied to an element, but generated implicitly by the
126     * framework.
127     */
128    SYNTHETIC,
129
130    // Provision kinds
131
132    /** An {@link Inject}-annotated constructor. */
133    INJECTION,
134
135    /** A {@link Provides}-annotated method. */
136    PROVISION,
137
138    /** An implicit binding to a {@link Component @Component}-annotated type. */
139    COMPONENT,
140
141    /** A provision method on a component's {@linkplain Component#dependencies() dependency}. */
142    COMPONENT_PROVISION,
143
144    /**
145     * A subcomponent builder method on a component or subcomponent.
146     */
147    SUBCOMPONENT_BUILDER,
148
149    // Production kinds
150
151    /** A {@link Produces}-annotated method that doesn't return a {@link ListenableFuture}. */
152    IMMEDIATE,
153
154    /** A {@link Produces}-annotated method that returns a {@link ListenableFuture}. */
155    FUTURE_PRODUCTION,
156
157    /**
158     * A production method on a production component's
159     * {@linkplain ProductionComponent#dependencies() dependency} that returns a
160     * {@link ListenableFuture}. Methods on production component dependencies that don't return a
161     * {@link ListenableFuture} are considered {@linkplain #PROVISION provision bindings}.
162     */
163    COMPONENT_PRODUCTION,
164  }
165
166  /**
167   * The kind of this contribution binding.
168   */
169  protected abstract Kind bindingKind();
170
171  /**
172   * A predicate that passes for bindings of a given kind.
173   */
174  static Predicate<ContributionBinding> isOfKind(final Kind kind) {
175    return new Predicate<ContributionBinding>() {
176      @Override
177      public boolean apply(ContributionBinding binding) {
178        return binding.bindingKind().equals(kind);
179      }};
180  }
181
182  /** The provision type that was used to bind the key. */
183  abstract Provides.Type provisionType();
184
185  /**
186   * The strategy for getting an instance of a factory for a {@link ContributionBinding}.
187   */
188  enum FactoryCreationStrategy {
189    /** The factory class is an enum with one value named {@code INSTANCE}. */
190    ENUM_INSTANCE,
191    /** The factory must be created by calling the constructor. */
192    CLASS_CONSTRUCTOR,
193  }
194
195  /**
196   * Returns {@link FactoryCreationStrategy#ENUM_INSTANCE} if the binding has no dependencies and
197   * is a static provision binding or an {@link Inject @Inject} constructor binding. Otherwise
198   * returns {@link FactoryCreationStrategy#CLASS_CONSTRUCTOR}.
199   */
200  FactoryCreationStrategy factoryCreationStrategy() {
201    switch (bindingKind()) {
202      case PROVISION:
203        return implicitDependencies().isEmpty() && bindingElement().getModifiers().contains(STATIC)
204            ? FactoryCreationStrategy.ENUM_INSTANCE
205            : FactoryCreationStrategy.CLASS_CONSTRUCTOR;
206
207      case INJECTION:
208        return implicitDependencies().isEmpty()
209            ? FactoryCreationStrategy.ENUM_INSTANCE
210            : FactoryCreationStrategy.CLASS_CONSTRUCTOR;
211
212      default:
213        return FactoryCreationStrategy.CLASS_CONSTRUCTOR;
214    }
215  }
216
217  /**
218   * Returns the {@link ContributionType}s represented by a given {@link ContributionBinding}
219   * collection.
220   */
221  static <B extends ContributionBinding>
222      ImmutableListMultimap<ContributionType, B> contributionTypesFor(
223          Iterable<? extends B> bindings) {
224    ImmutableListMultimap.Builder<ContributionType, B> builder = ImmutableListMultimap.builder();
225    builder.orderKeysBy(Ordering.<ContributionType>natural());
226    for (B binding : bindings) {
227      builder.put(binding.contributionType(), binding);
228    }
229    return builder.build();
230  }
231
232  /**
233   * Returns a single {@link ContributionType} represented by a given collection of
234   * {@link ContributionBinding}s.
235   *
236   * @throws IllegalArgumentException if the given bindings are not all of one type
237   */
238  static ContributionType contributionTypeFor(Iterable<ContributionBinding> bindings) {
239    checkNotNull(bindings);
240    checkArgument(!Iterables.isEmpty(bindings), "no bindings");
241    Set<ContributionType> types = EnumSet.noneOf(ContributionType.class);
242    for (ContributionBinding binding : bindings) {
243      types.add(binding.contributionType());
244    }
245    if (types.size() > 1) {
246      throw new IllegalArgumentException(
247          String.format(ErrorMessages.MULTIPLE_CONTRIBUTION_TYPES_FORMAT, types));
248    }
249    return Iterables.getOnlyElement(types);
250  }
251
252  /**
253   * Indexes map-multibindings by map key (the result of calling
254   * {@link AnnotationValue#getValue()} on a single member or the whole {@link AnnotationMirror}
255   * itself, depending on {@link MapKey#unwrapValue()}).
256   */
257  static ImmutableSetMultimap<Object, ContributionBinding> indexMapBindingsByMapKey(
258      Set<ContributionBinding> mapBindings) {
259    return ImmutableSetMultimap.copyOf(
260        Multimaps.index(
261            mapBindings,
262            new Function<ContributionBinding, Object>() {
263              @Override
264              public Object apply(ContributionBinding mapBinding) {
265                AnnotationMirror mapKey = getMapKey(mapBinding.bindingElement()).get();
266                Optional<? extends AnnotationValue> unwrappedValue = unwrapValue(mapKey);
267                return unwrappedValue.isPresent() ? unwrappedValue.get().getValue() : mapKey;
268              }
269            }));
270  }
271
272  /**
273   * Indexes map-multibindings by map key annotation type.
274   */
275  static ImmutableSetMultimap<Wrapper<DeclaredType>, ContributionBinding>
276      indexMapBindingsByAnnotationType(Set<ContributionBinding> mapBindings) {
277    return ImmutableSetMultimap.copyOf(
278        Multimaps.index(
279            mapBindings,
280            new Function<ContributionBinding, Equivalence.Wrapper<DeclaredType>>() {
281              @Override
282              public Equivalence.Wrapper<DeclaredType> apply(ContributionBinding mapBinding) {
283                return MoreTypes.equivalence()
284                    .wrap(getMapKey(mapBinding.bindingElement()).get().getAnnotationType());
285              }
286            }));
287  }
288}
289