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