• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.hilt.processor.internal.root;
18 
19 import static com.google.common.base.Preconditions.checkState;
20 import static com.google.common.base.Suppliers.memoize;
21 import static dagger.hilt.processor.internal.HiltCompilerOptions.isSharedTestComponentsEnabled;
22 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
23 import static javax.lang.model.element.Modifier.ABSTRACT;
24 import static javax.lang.model.element.Modifier.PRIVATE;
25 import static javax.lang.model.element.Modifier.STATIC;
26 
27 import com.google.common.base.Supplier;
28 import com.google.common.collect.ImmutableList;
29 import com.google.common.collect.ImmutableSet;
30 import com.google.common.collect.ImmutableSetMultimap;
31 import com.squareup.javapoet.ClassName;
32 import com.squareup.javapoet.TypeName;
33 import dagger.hilt.processor.internal.ClassNames;
34 import dagger.hilt.processor.internal.ComponentDescriptor;
35 import dagger.hilt.processor.internal.KotlinMetadataUtils;
36 import dagger.hilt.processor.internal.Processors;
37 import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies;
38 import dagger.hilt.processor.internal.aliasof.AliasOfs;
39 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
40 import java.util.List;
41 import javax.annotation.processing.ProcessingEnvironment;
42 import javax.lang.model.element.ExecutableElement;
43 import javax.lang.model.element.TypeElement;
44 import javax.lang.model.util.ElementFilter;
45 import javax.lang.model.util.Elements;
46 import javax.tools.Diagnostic;
47 
48 /** Contains metadata about the given hilt root. */
49 public final class RootMetadata {
50   private static final ClassName APPLICATION_CONTEXT_MODULE =
51       ClassName.get("dagger.hilt.android.internal.modules", "ApplicationContextModule");
52 
create( Root root, ComponentTree componentTree, ComponentDependencies deps, ProcessingEnvironment env)53   static RootMetadata create(
54       Root root,
55       ComponentTree componentTree,
56       ComponentDependencies deps,
57       ProcessingEnvironment env) {
58     return createInternal(root, ImmutableList.of(), componentTree, deps, env);
59   }
60 
createForDefaultRoot( Root root, ImmutableList<RootMetadata> rootsUsingDefaultComponents, ComponentTree componentTree, ComponentDependencies deps, ProcessingEnvironment env)61   static RootMetadata createForDefaultRoot(
62       Root root,
63       ImmutableList<RootMetadata> rootsUsingDefaultComponents,
64       ComponentTree componentTree,
65       ComponentDependencies deps,
66       ProcessingEnvironment env) {
67     checkState(root.isDefaultRoot());
68     return createInternal(root, rootsUsingDefaultComponents, componentTree, deps, env);
69   }
70 
copyWithNewTree(RootMetadata other, ComponentTree componentTree)71   static RootMetadata copyWithNewTree(RootMetadata other, ComponentTree componentTree) {
72     return createInternal(
73         other.root, other.rootsUsingDefaultComponents, componentTree, other.deps, other.env);
74   }
75 
createInternal( Root root, ImmutableList<RootMetadata> rootsUsingDefaultComponents, ComponentTree componentTree, ComponentDependencies deps, ProcessingEnvironment env)76   private static RootMetadata createInternal(
77       Root root,
78       ImmutableList<RootMetadata> rootsUsingDefaultComponents,
79       ComponentTree componentTree,
80       ComponentDependencies deps,
81       ProcessingEnvironment env) {
82     RootMetadata metadata =
83         new RootMetadata(root, componentTree, deps, rootsUsingDefaultComponents, env);
84     metadata.validate();
85     return metadata;
86   }
87 
88   private final Root root;
89   private final ProcessingEnvironment env;
90   private final Elements elements;
91   private final ComponentTree componentTree;
92   private final ComponentDependencies deps;
93   private final ImmutableList<RootMetadata> rootsUsingDefaultComponents;
94   private final Supplier<ImmutableSetMultimap<ClassName, ClassName>> scopesByComponent =
95       memoize(this::getScopesByComponentUncached);
96   private final Supplier<TestRootMetadata> testRootMetadata =
97       memoize(this::testRootMetadataUncached);
98 
RootMetadata( Root root, ComponentTree componentTree, ComponentDependencies deps, ImmutableList<RootMetadata> rootsUsingDefaultComponents, ProcessingEnvironment env)99   private RootMetadata(
100       Root root,
101       ComponentTree componentTree,
102       ComponentDependencies deps,
103       ImmutableList<RootMetadata> rootsUsingDefaultComponents,
104       ProcessingEnvironment env) {
105     this.root = root;
106     this.env = env;
107     this.elements = env.getElementUtils();
108     this.componentTree = componentTree;
109     this.deps = deps;
110     this.rootsUsingDefaultComponents = rootsUsingDefaultComponents;
111   }
112 
root()113   public Root root() {
114     return root;
115   }
116 
componentTree()117   public ComponentTree componentTree() {
118     return componentTree;
119   }
120 
deps()121   public ComponentDependencies deps() {
122     return deps;
123   }
124 
modules(ClassName componentName)125   public ImmutableSet<TypeElement> modules(ClassName componentName) {
126     return deps.modules().get(componentName, root.classname(), root.isTestRoot());
127   }
128 
129   /**
130    * Returns {@code true} if this is a test root that provides no test-specific dependencies or sets
131    * other options that would prevent it from sharing components with other test roots.
132    */
133   // TODO(groakley): Allow more tests to share modules, e.g. tests that uninstall the same module.
134   // In that case, this might instead return which shared dep grouping should be used.
canShareTestComponents()135   public boolean canShareTestComponents() {
136     return isSharedTestComponentsEnabled(env)
137         && root.isTestRoot()
138         && !deps.includesTestDeps(root.classname());
139   }
140 
entryPoints(ClassName componentName)141   public ImmutableSet<TypeName> entryPoints(ClassName componentName) {
142     return ImmutableSet.<TypeName>builder()
143         .addAll(getUserDefinedEntryPoints(componentName))
144         .add(
145             root.isTestRoot() && componentName.equals(ClassNames.SINGLETON_COMPONENT)
146                 ? ClassNames.TEST_SINGLETON_COMPONENT
147                 : ClassNames.GENERATED_COMPONENT)
148         .add(componentName)
149         .build();
150   }
151 
scopes(ClassName componentName)152   public ImmutableSet<ClassName> scopes(ClassName componentName) {
153     return scopesByComponent.get().get(componentName);
154   }
155 
156   /**
157    * Returns all modules in the given component that do not have accessible default constructors.
158    * Note that a non-static module nested in an outer class is considered to have no default
159    * constructors, since an instance of the outer class is needed to construct the module. This also
160    * filters out framework modules directly referenced by the codegen, since those are already known
161    * about and are specifically handled in the codegen.
162    */
modulesThatDaggerCannotConstruct(ClassName componentName)163   public ImmutableSet<TypeElement> modulesThatDaggerCannotConstruct(ClassName componentName) {
164     return modules(componentName).stream()
165         .filter(module -> !daggerCanConstruct(module))
166         .filter(module -> !APPLICATION_CONTEXT_MODULE.equals(ClassName.get(module)))
167         .collect(toImmutableSet());
168   }
169 
testRootMetadata()170   public TestRootMetadata testRootMetadata() {
171     checkState(!root.isDefaultRoot(), "The default root does not have TestRootMetadata!");
172     return testRootMetadata.get();
173   }
174 
waitForBindValue()175   public boolean waitForBindValue() {
176     return false;
177   }
178 
testRootMetadataUncached()179   private TestRootMetadata testRootMetadataUncached() {
180     return TestRootMetadata.of(env, root().element());
181   }
182 
183   /**
184    * Validates that the {@link RootType} annotation is compatible with its {@link TypeElement} and
185    * {@link ComponentDependencies}.
186    */
validate()187   private void validate() {
188 
189     // Only test modules in the application component can be missing default constructor
190     for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) {
191       ClassName componentName = componentDescriptor.component();
192       for (TypeElement extraModule : modulesThatDaggerCannotConstruct(componentName)) {
193         if (root.isTestRoot() && !componentName.equals(ClassNames.SINGLETON_COMPONENT)) {
194           env.getMessager()
195               .printMessage(
196                   Diagnostic.Kind.ERROR,
197                   "[Hilt] All test modules (unless installed in ApplicationComponent) must use "
198                       + "static provision methods or have a visible, no-arg constructor. Found: "
199                       + extraModule.getQualifiedName(),
200                   root.element());
201         } else if (!root.isTestRoot()) {
202           env.getMessager()
203               .printMessage(
204                   Diagnostic.Kind.ERROR,
205                   "[Hilt] All modules must be static and use static provision methods or have a "
206                       + "visible, no-arg constructor. Found: "
207                       + extraModule.getQualifiedName(),
208                   root.element());
209         }
210       }
211     }
212   }
213 
getUserDefinedEntryPoints(ClassName componentName)214   private ImmutableSet<TypeName> getUserDefinedEntryPoints(ClassName componentName) {
215     ImmutableSet.Builder<TypeName> entryPointSet = ImmutableSet.builder();
216     if (root.isDefaultRoot() && !rootsUsingDefaultComponents.isEmpty()) {
217       // Add entry points for shared component
218       rootsUsingDefaultComponents.stream()
219           .flatMap(metadata -> metadata.entryPoints(componentName).stream())
220           .forEach(entryPointSet::add);
221     } else if (root.isDefaultRoot() && componentName.equals(ClassNames.SINGLETON_COMPONENT)) {
222       // We only do this for SingletonComponent because EarlyEntryPoints can only be installed
223       // in the SingletonComponent.
224       deps.earlyEntryPoints().forEach(entryPointSet::add);
225     } else {
226       deps.entryPoints().get(componentName, root.classname(), root.isTestRoot()).stream()
227           .map(ClassName::get)
228           .forEach(entryPointSet::add);
229     }
230     return entryPointSet.build();
231   }
232 
getScopesByComponentUncached()233   private ImmutableSetMultimap<ClassName, ClassName> getScopesByComponentUncached() {
234     ImmutableSetMultimap.Builder<ClassName, ClassName> builder = ImmutableSetMultimap.builder();
235 
236     ImmutableSet<ClassName> defineComponentScopes =
237         componentTree.getComponentDescriptors().stream()
238             .flatMap(descriptor -> descriptor.scopes().stream())
239             .collect(toImmutableSet());
240 
241     AliasOfs aliasOfs = AliasOfs.create(env.getElementUtils(), defineComponentScopes);
242 
243     for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) {
244       for (ClassName scope : componentDescriptor.scopes()) {
245         builder.put(componentDescriptor.component(), scope);
246         builder.putAll(componentDescriptor.component(), aliasOfs.getAliasesFor(scope));
247       }
248     }
249 
250     return builder.build();
251   }
252 
daggerCanConstruct(TypeElement type)253   private static boolean daggerCanConstruct(TypeElement type) {
254     KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil();
255     boolean isKotlinObject =
256         metadataUtil.isObjectClass(type) || metadataUtil.isCompanionObjectClass(type);
257     if (isKotlinObject) {
258       // Treat Kotlin object modules as if Dagger can construct them (it technically can't, but it
259       // doesn't need to as it can use them since all their provision methods are static).
260       return true;
261     }
262 
263     return !isInnerClass(type)
264         && !hasNonDaggerAbstractMethod(type)
265         && (hasOnlyStaticProvides(type) || hasVisibleEmptyConstructor(type));
266   }
267 
isInnerClass(TypeElement type)268   private static boolean isInnerClass(TypeElement type) {
269     return type.getNestingKind().isNested() && !type.getModifiers().contains(STATIC);
270   }
271 
hasNonDaggerAbstractMethod(TypeElement type)272   private static boolean hasNonDaggerAbstractMethod(TypeElement type) {
273     // TODO(erichang): Actually this isn't really supported b/28989613
274     return ElementFilter.methodsIn(type.getEnclosedElements()).stream()
275         .filter(method -> method.getModifiers().contains(ABSTRACT))
276         .anyMatch(method -> !Processors.hasDaggerAbstractMethodAnnotation(method));
277   }
278 
hasOnlyStaticProvides(TypeElement type)279   private static boolean hasOnlyStaticProvides(TypeElement type) {
280     // TODO(erichang): Check for @Produces too when we have a producers story
281     return ElementFilter.methodsIn(type.getEnclosedElements()).stream()
282         .filter(method -> Processors.hasAnnotation(method, ClassNames.PROVIDES))
283         .allMatch(method -> method.getModifiers().contains(STATIC));
284   }
285 
hasVisibleEmptyConstructor(TypeElement type)286   private static boolean hasVisibleEmptyConstructor(TypeElement type) {
287     List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
288     return constructors.isEmpty()
289         || constructors.stream()
290             .filter(constructor -> constructor.getParameters().isEmpty())
291             .anyMatch(constructor -> !constructor.getModifiers().contains(PRIVATE));
292   }
293 }
294