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 23 import androidx.room.compiler.processing.XConstructorElement; 24 import androidx.room.compiler.processing.XProcessingEnv; 25 import androidx.room.compiler.processing.XTypeElement; 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.Processors; 34 import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies; 35 import dagger.hilt.processor.internal.aliasof.AliasOfs; 36 import java.util.List; 37 import javax.lang.model.element.TypeElement; 38 import javax.tools.Diagnostic; 39 40 /** Contains metadata about the given hilt root. */ 41 public final class RootMetadata { 42 43 private static final ClassName APPLICATION_CONTEXT_MODULE = 44 ClassName.get("dagger.hilt.android.internal.modules", "ApplicationContextModule"); 45 create( Root root, ComponentTree componentTree, ComponentDependencies deps, AliasOfs aliasOfs, XProcessingEnv env)46 static RootMetadata create( 47 Root root, 48 ComponentTree componentTree, 49 ComponentDependencies deps, 50 AliasOfs aliasOfs, 51 XProcessingEnv env) { 52 return createInternal(root, componentTree, deps, aliasOfs, env); 53 } 54 copyWithNewTree(RootMetadata other, ComponentTree componentTree)55 static RootMetadata copyWithNewTree(RootMetadata other, ComponentTree componentTree) { 56 return createInternal(other.root, componentTree, other.deps, other.aliasOfs, other.env); 57 } 58 createInternal( Root root, ComponentTree componentTree, ComponentDependencies deps, AliasOfs aliasOfs, XProcessingEnv env)59 private static RootMetadata createInternal( 60 Root root, 61 ComponentTree componentTree, 62 ComponentDependencies deps, 63 AliasOfs aliasOfs, 64 XProcessingEnv env) { 65 RootMetadata metadata = new RootMetadata(root, componentTree, deps, aliasOfs, env); 66 metadata.validate(); 67 return metadata; 68 } 69 70 private final Root root; 71 private final XProcessingEnv env; 72 private final ComponentTree componentTree; 73 private final ComponentDependencies deps; 74 private final AliasOfs aliasOfs; 75 private final Supplier<ImmutableSetMultimap<ClassName, ClassName>> scopesByComponent = 76 memoize(this::getScopesByComponentUncached); 77 private final Supplier<TestRootMetadata> testRootMetadata = 78 memoize(this::testRootMetadataUncached); 79 RootMetadata( Root root, ComponentTree componentTree, ComponentDependencies deps, AliasOfs aliasOfs, XProcessingEnv env)80 private RootMetadata( 81 Root root, 82 ComponentTree componentTree, 83 ComponentDependencies deps, 84 AliasOfs aliasOfs, 85 XProcessingEnv env) { 86 this.root = root; 87 this.env = env; 88 this.componentTree = componentTree; 89 this.deps = deps; 90 this.aliasOfs = aliasOfs; 91 } 92 root()93 public Root root() { 94 return root; 95 } 96 componentTree()97 public ComponentTree componentTree() { 98 return componentTree; 99 } 100 deps()101 public ComponentDependencies deps() { 102 return deps; 103 } 104 modules(ClassName componentName)105 public ImmutableSet<XTypeElement> modules(ClassName componentName) { 106 return deps.modules().get(componentName).stream().collect(toImmutableSet()); 107 } 108 entryPoints(ClassName componentName)109 public ImmutableSet<TypeName> entryPoints(ClassName componentName) { 110 return ImmutableSet.<TypeName>builder() 111 .addAll( 112 deps.entryPoints().get(componentName).stream() 113 .map(XTypeElement::getClassName) 114 .collect(toImmutableSet())) 115 .add( 116 root.isTestRoot() && componentName.equals(ClassNames.SINGLETON_COMPONENT) 117 ? ClassNames.TEST_SINGLETON_COMPONENT 118 : ClassNames.GENERATED_COMPONENT) 119 .add(componentName) 120 .build(); 121 } 122 scopes(ClassName componentName)123 public ImmutableSet<ClassName> scopes(ClassName componentName) { 124 return scopesByComponent.get().get(componentName); 125 } 126 127 /** 128 * Returns all modules in the given component that do not have accessible default constructors. 129 * Note that a non-static module nested in an outer class is considered to have no default 130 * constructors, since an instance of the outer class is needed to construct the module. This also 131 * filters out framework modules directly referenced by the codegen, since those are already known 132 * about and are specifically handled in the codegen. 133 */ modulesThatDaggerCannotConstruct(ClassName componentName)134 public ImmutableSet<XTypeElement> modulesThatDaggerCannotConstruct(ClassName componentName) { 135 return modules(componentName).stream() 136 .filter(module -> !daggerCanConstruct(module)) 137 .filter(module -> !APPLICATION_CONTEXT_MODULE.equals(module.getClassName())) 138 .collect(toImmutableSet()); 139 } 140 testRootMetadata()141 public TestRootMetadata testRootMetadata() { 142 checkState(!root.isDefaultRoot(), "The default root does not have TestRootMetadata!"); 143 return testRootMetadata.get(); 144 } 145 waitForBindValue()146 public boolean waitForBindValue() { 147 return false; 148 } 149 testRootMetadataUncached()150 private TestRootMetadata testRootMetadataUncached() { 151 return TestRootMetadata.of(env, root().element()); 152 } 153 154 /** 155 * Validates that the {@link RootType} annotation is compatible with its {@link TypeElement} and 156 * {@link ComponentDependencies}. 157 */ validate()158 private void validate() { 159 160 // Only test modules in the application component can be missing default constructor 161 for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) { 162 ClassName componentName = componentDescriptor.component(); 163 for (XTypeElement extraModule : modulesThatDaggerCannotConstruct(componentName)) { 164 if (root.isTestRoot() && !componentName.equals(ClassNames.SINGLETON_COMPONENT)) { 165 env.getMessager() 166 .printMessage( 167 Diagnostic.Kind.ERROR, 168 "[Hilt] All test modules (unless installed in ApplicationComponent) must use " 169 + "static provision methods or have a visible, no-arg constructor. Found: " 170 + extraModule.getQualifiedName(), 171 root.originatingRootElement()); 172 } else if (!root.isTestRoot()) { 173 env.getMessager() 174 .printMessage( 175 Diagnostic.Kind.ERROR, 176 "[Hilt] All modules must be static and use static provision methods or have a " 177 + "visible, no-arg constructor. Found: " 178 + extraModule.getQualifiedName(), 179 root.originatingRootElement()); 180 } 181 } 182 } 183 } 184 getScopesByComponentUncached()185 private ImmutableSetMultimap<ClassName, ClassName> getScopesByComponentUncached() { 186 ImmutableSetMultimap.Builder<ClassName, ClassName> builder = ImmutableSetMultimap.builder(); 187 for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) { 188 for (ClassName scope : componentDescriptor.scopes()) { 189 builder.put(componentDescriptor.component(), scope); 190 builder.putAll(componentDescriptor.component(), aliasOfs.getAliasesFor(scope)); 191 } 192 } 193 194 return builder.build(); 195 } 196 daggerCanConstruct(XTypeElement type)197 private static boolean daggerCanConstruct(XTypeElement type) { 198 if (!Processors.requiresModuleInstance(type)) { 199 return true; 200 } 201 return hasVisibleEmptyConstructor(type) && (!type.isNested() || type.isStatic()); 202 } 203 hasVisibleEmptyConstructor(XTypeElement type)204 private static boolean hasVisibleEmptyConstructor(XTypeElement type) { 205 List<XConstructorElement> constructors = type.getConstructors(); 206 return constructors.isEmpty() 207 || constructors.stream() 208 .filter(constructor -> constructor.getParameters().isEmpty()) 209 .anyMatch(constructor -> !constructor.isPrivate()); 210 } 211 } 212