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