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.MoreElements;
19import com.google.auto.common.MoreTypes;
20import com.google.common.base.Optional;
21import com.google.common.base.Predicate;
22import com.google.common.collect.FluentIterable;
23import com.google.common.collect.ImmutableSet;
24import com.google.common.collect.Iterables;
25import com.google.common.collect.Maps;
26import com.google.common.collect.Sets;
27import dagger.Component;
28import dagger.Provides;
29import dagger.internal.codegen.writer.ClassName;
30import java.util.ArrayDeque;
31import java.util.Deque;
32import java.util.List;
33import java.util.Map;
34import java.util.Set;
35import javax.annotation.processing.Messager;
36import javax.inject.Inject;
37import javax.lang.model.element.ExecutableElement;
38import javax.lang.model.element.TypeElement;
39import javax.lang.model.util.ElementFilter;
40import javax.lang.model.util.Elements;
41import javax.lang.model.util.Types;
42import javax.tools.Diagnostic.Kind;
43
44import static com.google.auto.common.MoreElements.isAnnotationPresent;
45import static com.google.common.base.Preconditions.checkArgument;
46import static com.google.common.base.Preconditions.checkNotNull;
47import static com.google.common.base.Preconditions.checkState;
48import 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 */
58final 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
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. */
84    B getBinding(Key key) {
85      return bindingsByKey.get(key);
86    }
87
88    /** Caches the binding and generates it if it needs generation. */
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     */
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. */
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. */
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
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   */
155  void generateSourcesForRequiredBindings(FactoryGenerator factoryGenerator,
156      MembersInjectorGenerator membersInjectorGenerator) throws SourceFileGenerationException {
157    provisionBindings.generateBindings(factoryGenerator);
158    membersInjectionBindings.generateBindings(membersInjectorGenerator);
159  }
160
161  ProvisionBinding registerBinding(ProvisionBinding binding) {
162    return registerBinding(binding, true);
163  }
164
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   */
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   */
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
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
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