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