• 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.MoreElements;
19 import com.google.auto.common.MoreTypes;
20 import com.google.common.base.Optional;
21 import com.google.common.base.Predicate;
22 import com.google.common.collect.FluentIterable;
23 import com.google.common.collect.ImmutableSet;
24 import com.google.common.collect.Iterables;
25 import com.google.common.collect.Maps;
26 import com.google.common.collect.Sets;
27 import dagger.Component;
28 import dagger.Provides;
29 import dagger.internal.codegen.writer.ClassName;
30 import java.util.ArrayDeque;
31 import java.util.Deque;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import javax.annotation.processing.Messager;
36 import javax.inject.Inject;
37 import javax.lang.model.element.ExecutableElement;
38 import javax.lang.model.element.TypeElement;
39 import javax.lang.model.util.ElementFilter;
40 import javax.lang.model.util.Elements;
41 import javax.lang.model.util.Types;
42 import javax.tools.Diagnostic.Kind;
43 
44 import static com.google.auto.common.MoreElements.isAnnotationPresent;
45 import static com.google.common.base.Preconditions.checkArgument;
46 import static com.google.common.base.Preconditions.checkNotNull;
47 import static com.google.common.base.Preconditions.checkState;
48 import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding;
49 
50 /**
51  * Maintains the collection of provision bindings from {@link Inject} constructors and members
52  * injection bindings from {@link Inject} fields and methods known to the annotation processor.
53  * Note that this registry <b>does not</b> handle any explicit bindings (those from {@link Provides}
54  * methods, {@link Component} dependencies, etc.).
55  *
56  * @author Gregory Kick
57  */
58 final class InjectBindingRegistry {
59   private final Elements elements;
60   private final Types types;
61   private final Messager messager;
62   private final ProvisionBinding.Factory provisionBindingFactory;
63   private final MembersInjectionBinding.Factory membersInjectionBindingFactory;
64 
65   final class BindingsCollection<B extends Binding> {
66     private final Map<Key, B> bindingsByKey = Maps.newLinkedHashMap();
67     private final Deque<B> bindingsRequiringGeneration = new ArrayDeque<>();
68     private final Set<Key> materializedBindingKeys = Sets.newLinkedHashSet();
69 
generateBindings(SourceFileGenerator<B> generator)70     void generateBindings(SourceFileGenerator<B> generator) throws SourceFileGenerationException {
71       for (B binding = bindingsRequiringGeneration.poll();
72           binding != null;
73           binding = bindingsRequiringGeneration.poll()) {
74         checkState(!binding.hasNonDefaultTypeParameters());
75         generator.generate(binding);
76         materializedBindingKeys.add(binding.key());
77       }
78       // Because Elements instantiated across processing rounds are not guaranteed to be equals() to
79       // the logically same element, clear the cache after generating
80       bindingsByKey.clear();
81     }
82 
83     /** Returns a previously cached binding. */
getBinding(Key key)84     B getBinding(Key key) {
85       return bindingsByKey.get(key);
86     }
87 
88     /** Caches the binding and generates it if it needs generation. */
tryRegisterBinding(B binding, ClassName factoryName, boolean explicit)89     void tryRegisterBinding(B binding, ClassName factoryName, boolean explicit) {
90       tryToCacheBinding(binding);
91       tryToGenerateBinding(binding, factoryName, explicit);
92     }
93 
94     /**
95      * Tries to generate a binding, not generating if it already is generated. For resolved
96      * bindings, this will try to generate the unresolved version of the binding.
97      */
tryToGenerateBinding(B binding, ClassName factoryName, boolean explicit)98     void tryToGenerateBinding(B binding, ClassName factoryName, boolean explicit) {
99       if (shouldGenerateBinding(binding, factoryName)) {
100         bindingsRequiringGeneration.offer(binding);
101         if (!explicit) {
102           messager.printMessage(Kind.NOTE, String.format(
103               "Generating a MembersInjector or Factory for %s. "
104                     + "Prefer to run the dagger processor over that class instead.",
105               types.erasure(binding.key().type()))); // erasure to strip <T> from msgs.
106         }
107       }
108     }
109 
110     /** Returns true if the binding needs to be generated. */
shouldGenerateBinding(B binding, ClassName factoryName)111     private boolean shouldGenerateBinding(B binding, ClassName factoryName) {
112       return !binding.hasNonDefaultTypeParameters()
113           && elements.getTypeElement(factoryName.canonicalName()) == null
114           && !materializedBindingKeys.contains(binding.key())
115           && !bindingsRequiringGeneration.contains(binding);
116 
117     }
118 
119     /** Caches the binding for future lookups by key. */
tryToCacheBinding(B binding)120     private void tryToCacheBinding(B binding) {
121       // We only cache resolved bindings or unresolved bindings w/o type arguments.
122       // Unresolved bindings w/ type arguments aren't valid for the object graph.
123       if (binding.hasNonDefaultTypeParameters()
124           || binding.bindingTypeElement().getTypeParameters().isEmpty()) {
125         Key key = binding.key();
126         Binding previousValue = bindingsByKey.put(key, binding);
127         checkState(previousValue == null || binding.equals(previousValue),
128             "couldn't register %s. %s was already registered for %s",
129             binding, previousValue, key);
130       }
131     }
132   }
133 
134   private final BindingsCollection<ProvisionBinding> provisionBindings = new BindingsCollection<>();
135   private final BindingsCollection<MembersInjectionBinding> membersInjectionBindings =
136       new BindingsCollection<>();
137 
InjectBindingRegistry(Elements elements, Types types, Messager messager, ProvisionBinding.Factory provisionBindingFactory, MembersInjectionBinding.Factory membersInjectionBindingFactory)138   InjectBindingRegistry(Elements elements,
139       Types types,
140       Messager messager,
141       ProvisionBinding.Factory provisionBindingFactory,
142       MembersInjectionBinding.Factory membersInjectionBindingFactory) {
143     this.elements = elements;
144     this.types = types;
145     this.messager = messager;
146     this.provisionBindingFactory = provisionBindingFactory;
147     this.membersInjectionBindingFactory = membersInjectionBindingFactory;
148   }
149 
150   /**
151    * This method ensures that sources for all registered {@link Binding bindings} (either
152    * {@linkplain #registerBinding explicitly} or implicitly via
153    * {@link #getOrFindMembersInjectionBinding} or {@link #getOrFindProvisionBinding}) are generated.
154    */
generateSourcesForRequiredBindings(FactoryGenerator factoryGenerator, MembersInjectorGenerator membersInjectorGenerator)155   void generateSourcesForRequiredBindings(FactoryGenerator factoryGenerator,
156       MembersInjectorGenerator membersInjectorGenerator) throws SourceFileGenerationException {
157     provisionBindings.generateBindings(factoryGenerator);
158     membersInjectionBindings.generateBindings(membersInjectorGenerator);
159   }
160 
registerBinding(ProvisionBinding binding)161   ProvisionBinding registerBinding(ProvisionBinding binding) {
162     return registerBinding(binding, true);
163   }
164 
registerBinding(MembersInjectionBinding binding)165   MembersInjectionBinding registerBinding(MembersInjectionBinding binding) {
166     return registerBinding(binding, true);
167   }
168 
169   /**
170    * Registers the binding for generation & later lookup. If the binding is resolved, we also
171    * attempt to register an unresolved version of it.
172    */
registerBinding(ProvisionBinding binding, boolean explicit)173   private ProvisionBinding registerBinding(ProvisionBinding binding, boolean explicit) {
174     ClassName factoryName = generatedClassNameForBinding(binding);
175     provisionBindings.tryRegisterBinding(binding, factoryName, explicit);
176     if (binding.hasNonDefaultTypeParameters()) {
177       provisionBindings.tryToGenerateBinding(provisionBindingFactory.unresolve(binding),
178           factoryName, explicit);
179     }
180     return binding;
181   }
182 
183   /**
184    * Registers the binding for generation & later lookup. If the binding is resolved, we also
185    * attempt to register an unresolved version of it.
186    */
registerBinding( MembersInjectionBinding binding, boolean explicit)187   private MembersInjectionBinding registerBinding(
188       MembersInjectionBinding binding, boolean explicit) {
189     ClassName membersInjectorName = generatedClassNameForBinding(binding);
190     membersInjectionBindings.tryRegisterBinding(binding, membersInjectorName, explicit);
191     if (binding.hasNonDefaultTypeParameters()) {
192       membersInjectionBindings.tryToGenerateBinding(
193           membersInjectionBindingFactory.unresolve(binding), membersInjectorName, explicit);
194     }
195     return binding;
196   }
197 
getOrFindProvisionBinding(Key key)198   Optional<ProvisionBinding> getOrFindProvisionBinding(Key key) {
199     checkNotNull(key);
200     if (!key.isValidImplicitProvisionKey(types)) {
201       return Optional.absent();
202     }
203     ProvisionBinding binding = provisionBindings.getBinding(key);
204     if (binding != null) {
205       return Optional.of(binding);
206     }
207 
208     // ok, let's see if we can find an @Inject constructor
209     TypeElement element = MoreElements.asType(types.asElement(key.type()));
210     List<ExecutableElement> constructors =
211         ElementFilter.constructorsIn(element.getEnclosedElements());
212     ImmutableSet<ExecutableElement> injectConstructors = FluentIterable.from(constructors)
213         .filter(new Predicate<ExecutableElement>() {
214           @Override public boolean apply(ExecutableElement input) {
215             return isAnnotationPresent(input, Inject.class);
216           }
217         }).toSet();
218     switch (injectConstructors.size()) {
219       case 0:
220         // No constructor found.
221         return Optional.absent();
222       case 1:
223         ProvisionBinding constructorBinding = provisionBindingFactory.forInjectConstructor(
224             Iterables.getOnlyElement(injectConstructors), Optional.of(key.type()));
225         return Optional.of(registerBinding(constructorBinding, false));
226       default:
227         throw new IllegalStateException("Found multiple @Inject constructors: "
228             + injectConstructors);
229     }
230   }
231 
getOrFindMembersInjectionBinding(Key key)232   MembersInjectionBinding getOrFindMembersInjectionBinding(Key key) {
233     checkNotNull(key);
234     // TODO(gak): is checking the kind enough?
235     checkArgument(key.isValidMembersInjectionKey());
236     MembersInjectionBinding binding = membersInjectionBindings.getBinding(key);
237     if (binding != null) {
238       return binding;
239     }
240     return registerBinding(membersInjectionBindingFactory.forInjectedType(
241         MoreTypes.asDeclared(key.type()), Optional.of(key.type())), false);
242   }
243 }
244