• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
16 package dagger.internal.codegen;
17 
18 import com.google.auto.common.MoreTypes;
19 import com.google.common.base.Equivalence;
20 import com.google.common.base.Equivalence.Wrapper;
21 import com.google.common.base.Function;
22 import com.google.common.base.Optional;
23 import com.google.common.base.Predicate;
24 import com.google.common.collect.ImmutableListMultimap;
25 import com.google.common.collect.ImmutableSetMultimap;
26 import com.google.common.collect.Iterables;
27 import com.google.common.collect.Multimaps;
28 import com.google.common.collect.Ordering;
29 import com.google.common.collect.Sets;
30 import com.google.common.util.concurrent.ListenableFuture;
31 import dagger.Component;
32 import dagger.MapKey;
33 import dagger.Provides;
34 import dagger.producers.Produces;
35 import dagger.producers.ProductionComponent;
36 import java.util.EnumSet;
37 import java.util.Set;
38 import javax.inject.Inject;
39 import javax.lang.model.element.AnnotationMirror;
40 import javax.lang.model.element.AnnotationValue;
41 import javax.lang.model.element.TypeElement;
42 import javax.lang.model.type.DeclaredType;
43 
44 import static com.google.common.base.Preconditions.checkArgument;
45 import static com.google.common.base.Preconditions.checkNotNull;
46 import static dagger.internal.codegen.MapKeys.getMapKey;
47 import static dagger.internal.codegen.MapKeys.unwrapValue;
48 import 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  */
57 abstract class ContributionBinding extends Binding {
58 
59   @Override
implicitDependencies()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 
isMultibinding()78     boolean isMultibinding() {
79       return !this.equals(UNIQUE);
80     }
81   }
82 
contributionType()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. */
nullableType()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    */
contributedBy()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    */
isSyntheticBinding()112   boolean isSyntheticBinding() {
113     return bindingKind().equals(Kind.SYNTHETIC);
114   }
115 
116   /** If this provision requires members injection, this will be the corresponding request. */
membersInjectionRequest()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    */
bindingKind()169   protected abstract Kind bindingKind();
170 
171   /**
172    * A predicate that passes for bindings of a given kind.
173    */
isOfKind(final Kind kind)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