• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Dagger Authors.
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 
17 package dagger.internal.codegen.binding;
18 
19 import static com.google.auto.common.MoreElements.asExecutable;
20 import static com.google.auto.common.MoreElements.getPackage;
21 import static com.google.auto.common.MoreElements.isAnnotationPresent;
22 import static com.google.common.base.CaseFormat.LOWER_CAMEL;
23 import static com.google.common.base.CaseFormat.UPPER_CAMEL;
24 import static com.google.common.base.Preconditions.checkArgument;
25 import static com.google.common.base.Verify.verify;
26 import static com.google.common.collect.Iterables.transform;
27 import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation;
28 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
29 import static dagger.internal.codegen.binding.SourceFiles.classFileName;
30 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
31 import static dagger.internal.codegen.langmodel.DaggerElements.getMethodDescriptor;
32 import static dagger.internal.codegen.langmodel.DaggerElements.isAnnotationPresent;
33 import static javax.lang.model.type.TypeKind.DECLARED;
34 import static javax.lang.model.type.TypeKind.NONE;
35 import static javax.lang.model.util.ElementFilter.methodsIn;
36 
37 import com.google.auto.common.MoreElements;
38 import com.google.auto.common.MoreTypes;
39 import com.google.auto.value.AutoValue;
40 import com.google.auto.value.extension.memoized.Memoized;
41 import com.google.common.collect.ImmutableSet;
42 import com.google.common.graph.Traverser;
43 import com.google.errorprone.annotations.CanIgnoreReturnValue;
44 import com.squareup.javapoet.ClassName;
45 import dagger.Binds;
46 import dagger.BindsOptionalOf;
47 import dagger.Module;
48 import dagger.Provides;
49 import dagger.internal.codegen.base.ClearableCache;
50 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
51 import dagger.internal.codegen.langmodel.DaggerElements;
52 import dagger.model.Key;
53 import dagger.multibindings.Multibinds;
54 import dagger.producers.Produces;
55 import java.util.HashMap;
56 import java.util.LinkedHashSet;
57 import java.util.Map;
58 import java.util.Set;
59 import javax.inject.Inject;
60 import javax.inject.Singleton;
61 import javax.lang.model.element.ExecutableElement;
62 import javax.lang.model.element.TypeElement;
63 import javax.lang.model.type.TypeMirror;
64 
65 /** Contains metadata that describes a module. */
66 @AutoValue
67 public abstract class ModuleDescriptor {
68 
moduleElement()69   public abstract TypeElement moduleElement();
70 
includedModules()71   abstract ImmutableSet<TypeElement> includedModules();
72 
bindings()73   public abstract ImmutableSet<ContributionBinding> bindings();
74 
75   /** The multibinding declarations contained in this module. */
multibindingDeclarations()76   abstract ImmutableSet<MultibindingDeclaration> multibindingDeclarations();
77 
78   /** The {@link Module#subcomponents() subcomponent declarations} contained in this module. */
subcomponentDeclarations()79   abstract ImmutableSet<SubcomponentDeclaration> subcomponentDeclarations();
80 
81   /** The {@link Binds} method declarations that define delegate bindings. */
delegateDeclarations()82   abstract ImmutableSet<DelegateDeclaration> delegateDeclarations();
83 
84   /** The {@link BindsOptionalOf} method declarations that define optional bindings. */
optionalDeclarations()85   abstract ImmutableSet<OptionalBindingDeclaration> optionalDeclarations();
86 
87   /** The kind of the module. */
kind()88   public abstract ModuleKind kind();
89 
90   /** Returns all of the bindings declared in this module. */
91   @Memoized
allBindingDeclarations()92   public ImmutableSet<BindingDeclaration> allBindingDeclarations() {
93     return ImmutableSet.<BindingDeclaration>builder()
94         .addAll(bindings())
95         .addAll(delegateDeclarations())
96         .addAll(multibindingDeclarations())
97         .addAll(optionalDeclarations())
98         .addAll(subcomponentDeclarations())
99         .build();
100   }
101 
102   /** Returns the keys of all bindings declared by this module. */
allBindingKeys()103   ImmutableSet<Key> allBindingKeys() {
104     return allBindingDeclarations().stream().map(BindingDeclaration::key).collect(toImmutableSet());
105   }
106 
107   /** A {@link ModuleDescriptor} factory. */
108   @Singleton
109   public static final class Factory implements ClearableCache {
110     private final DaggerElements elements;
111     private final KotlinMetadataUtil metadataUtil;
112     private final BindingFactory bindingFactory;
113     private final MultibindingDeclaration.Factory multibindingDeclarationFactory;
114     private final DelegateDeclaration.Factory bindingDelegateDeclarationFactory;
115     private final SubcomponentDeclaration.Factory subcomponentDeclarationFactory;
116     private final OptionalBindingDeclaration.Factory optionalBindingDeclarationFactory;
117     private final Map<TypeElement, ModuleDescriptor> cache = new HashMap<>();
118 
119     @Inject
Factory( DaggerElements elements, KotlinMetadataUtil metadataUtil, BindingFactory bindingFactory, MultibindingDeclaration.Factory multibindingDeclarationFactory, DelegateDeclaration.Factory bindingDelegateDeclarationFactory, SubcomponentDeclaration.Factory subcomponentDeclarationFactory, OptionalBindingDeclaration.Factory optionalBindingDeclarationFactory)120     Factory(
121         DaggerElements elements,
122         KotlinMetadataUtil metadataUtil,
123         BindingFactory bindingFactory,
124         MultibindingDeclaration.Factory multibindingDeclarationFactory,
125         DelegateDeclaration.Factory bindingDelegateDeclarationFactory,
126         SubcomponentDeclaration.Factory subcomponentDeclarationFactory,
127         OptionalBindingDeclaration.Factory optionalBindingDeclarationFactory) {
128       this.elements = elements;
129       this.metadataUtil = metadataUtil;
130       this.bindingFactory = bindingFactory;
131       this.multibindingDeclarationFactory = multibindingDeclarationFactory;
132       this.bindingDelegateDeclarationFactory = bindingDelegateDeclarationFactory;
133       this.subcomponentDeclarationFactory = subcomponentDeclarationFactory;
134       this.optionalBindingDeclarationFactory = optionalBindingDeclarationFactory;
135     }
136 
create(TypeElement moduleElement)137     public ModuleDescriptor create(TypeElement moduleElement) {
138       return reentrantComputeIfAbsent(cache, moduleElement, this::createUncached);
139     }
140 
createUncached(TypeElement moduleElement)141     public ModuleDescriptor createUncached(TypeElement moduleElement) {
142       ImmutableSet.Builder<ContributionBinding> bindings = ImmutableSet.builder();
143       ImmutableSet.Builder<DelegateDeclaration> delegates = ImmutableSet.builder();
144       ImmutableSet.Builder<MultibindingDeclaration> multibindingDeclarations =
145           ImmutableSet.builder();
146       ImmutableSet.Builder<OptionalBindingDeclaration> optionalDeclarations =
147           ImmutableSet.builder();
148 
149       for (ExecutableElement moduleMethod : methodsIn(elements.getAllMembers(moduleElement))) {
150         if (isAnnotationPresent(moduleMethod, Provides.class)) {
151           bindings.add(bindingFactory.providesMethodBinding(moduleMethod, moduleElement));
152         }
153         if (isAnnotationPresent(moduleMethod, Produces.class)) {
154           bindings.add(bindingFactory.producesMethodBinding(moduleMethod, moduleElement));
155         }
156         if (isAnnotationPresent(moduleMethod, Binds.class)) {
157           delegates.add(bindingDelegateDeclarationFactory.create(moduleMethod, moduleElement));
158         }
159         if (isAnnotationPresent(moduleMethod, Multibinds.class)) {
160           multibindingDeclarations.add(
161               multibindingDeclarationFactory.forMultibindsMethod(moduleMethod, moduleElement));
162         }
163         if (isAnnotationPresent(moduleMethod, BindsOptionalOf.class)) {
164           optionalDeclarations.add(
165               optionalBindingDeclarationFactory.forMethod(moduleMethod, moduleElement));
166         }
167       }
168 
169       if (metadataUtil.hasEnclosedCompanionObject(moduleElement)) {
170         collectCompanionModuleBindings(moduleElement, bindings);
171       }
172 
173       return new AutoValue_ModuleDescriptor(
174           moduleElement,
175           ImmutableSet.copyOf(collectIncludedModules(new LinkedHashSet<>(), moduleElement)),
176           bindings.build(),
177           multibindingDeclarations.build(),
178           subcomponentDeclarationFactory.forModule(moduleElement),
179           delegates.build(),
180           optionalDeclarations.build(),
181           ModuleKind.forAnnotatedElement(moduleElement).get());
182     }
183 
collectCompanionModuleBindings( TypeElement moduleElement, ImmutableSet.Builder<ContributionBinding> bindings)184     private void collectCompanionModuleBindings(
185         TypeElement moduleElement, ImmutableSet.Builder<ContributionBinding> bindings) {
186       checkArgument(metadataUtil.hasEnclosedCompanionObject(moduleElement));
187       TypeElement companionModule = metadataUtil.getEnclosedCompanionObject(moduleElement);
188       ImmutableSet<String> bindingElementDescriptors =
189           bindings.build().stream()
190               .map(binding -> getMethodDescriptor(asExecutable(binding.bindingElement().get())))
191               .collect(toImmutableSet());
192       methodsIn(elements.getAllMembers(companionModule)).stream()
193           // Binding methods in companion objects with @JvmStatic are mirrored in the enclosing
194           // class, therefore we should ignore it or else it'll be a duplicate binding.
195           .filter(method -> !KotlinMetadataUtil.isJvmStaticPresent(method))
196           // Fallback strategy for de-duping contributing bindings in the companion module with
197           // @JvmStatic by comparing descriptors. Contributing bindings are the only valid bindings
198           // a companion module can declare. See: https://youtrack.jetbrains.com/issue/KT-35104
199           // TODO(danysantiago): Checks qualifiers too.
200           .filter(method -> !bindingElementDescriptors.contains(getMethodDescriptor(method)))
201           .forEach(
202               method -> {
203                 if (isAnnotationPresent(method, Provides.class)) {
204                   bindings.add(bindingFactory.providesMethodBinding(method, companionModule));
205                 }
206                 if (isAnnotationPresent(method, Produces.class)) {
207                   bindings.add(bindingFactory.producesMethodBinding(method, companionModule));
208                 }
209               });
210     }
211 
212     /** Returns all the modules transitively included by given modules, including the arguments. */
transitiveModules(Iterable<TypeElement> modules)213     ImmutableSet<ModuleDescriptor> transitiveModules(Iterable<TypeElement> modules) {
214       return ImmutableSet.copyOf(
215           Traverser.forGraph(
216                   (ModuleDescriptor module) -> transform(module.includedModules(), this::create))
217               .depthFirstPreOrder(transform(modules, this::create)));
218     }
219 
220     @CanIgnoreReturnValue
collectIncludedModules( Set<TypeElement> includedModules, TypeElement moduleElement)221     private Set<TypeElement> collectIncludedModules(
222         Set<TypeElement> includedModules, TypeElement moduleElement) {
223       TypeMirror superclass = moduleElement.getSuperclass();
224       if (!superclass.getKind().equals(NONE)) {
225         verify(superclass.getKind().equals(DECLARED));
226         TypeElement superclassElement = MoreTypes.asTypeElement(superclass);
227         if (!superclassElement.getQualifiedName().contentEquals(Object.class.getCanonicalName())) {
228           collectIncludedModules(includedModules, superclassElement);
229         }
230       }
231       moduleAnnotation(moduleElement)
232           .ifPresent(
233               moduleAnnotation -> {
234                 includedModules.addAll(moduleAnnotation.includes());
235                 includedModules.addAll(implicitlyIncludedModules(moduleElement));
236               });
237       return includedModules;
238     }
239 
240     // @ContributesAndroidInjector generates a module that is implicitly included in the enclosing
241     // module
implicitlyIncludedModules(TypeElement moduleElement)242     private ImmutableSet<TypeElement> implicitlyIncludedModules(TypeElement moduleElement) {
243       TypeElement contributesAndroidInjector =
244           elements.getTypeElement("dagger.android.ContributesAndroidInjector");
245       if (contributesAndroidInjector == null) {
246         return ImmutableSet.of();
247       }
248       return methodsIn(moduleElement.getEnclosedElements()).stream()
249           .filter(method -> isAnnotationPresent(method, contributesAndroidInjector.asType()))
250           .map(method -> elements.checkTypePresent(implicitlyIncludedModuleName(method)))
251           .collect(toImmutableSet());
252     }
253 
implicitlyIncludedModuleName(ExecutableElement method)254     private String implicitlyIncludedModuleName(ExecutableElement method) {
255       return getPackage(method).getQualifiedName()
256           + "."
257           + classFileName(ClassName.get(MoreElements.asType(method.getEnclosingElement())))
258           + "_"
259           + LOWER_CAMEL.to(UPPER_CAMEL, method.getSimpleName().toString());
260     }
261 
262     @Override
clearCache()263     public void clearCache() {
264       cache.clear();
265     }
266   }
267 }
268