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