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