Home | History | Annotate | Download | only in codegen
      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 
     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