1 /* 2 * Copyright (C) 2015 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.internal.codegen.validation; 18 19 import static com.google.common.base.Functions.constant; 20 import static com.google.common.base.Predicates.and; 21 import static com.google.common.base.Predicates.in; 22 import static com.google.common.base.Predicates.not; 23 import static dagger.internal.codegen.base.Scopes.getReadableSource; 24 import static dagger.internal.codegen.base.Scopes.uniqueScopeOf; 25 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 26 27 import com.google.auto.common.MoreTypes; 28 import com.google.common.base.Joiner; 29 import com.google.common.base.Predicate; 30 import com.google.common.collect.FluentIterable; 31 import com.google.common.collect.ImmutableMap; 32 import com.google.common.collect.ImmutableSet; 33 import com.google.common.collect.ImmutableSetMultimap; 34 import com.google.common.collect.Iterables; 35 import com.google.common.collect.LinkedHashMultimap; 36 import com.google.common.collect.Maps; 37 import com.google.common.collect.Multimaps; 38 import com.google.common.collect.SetMultimap; 39 import com.google.common.collect.Sets; 40 import dagger.internal.codegen.binding.ComponentDescriptor; 41 import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; 42 import dagger.internal.codegen.binding.ModuleDescriptor; 43 import dagger.internal.codegen.binding.ModuleKind; 44 import dagger.internal.codegen.compileroption.CompilerOptions; 45 import dagger.model.Scope; 46 import java.util.Collection; 47 import java.util.Formatter; 48 import java.util.Map; 49 import javax.inject.Inject; 50 import javax.lang.model.element.TypeElement; 51 import javax.lang.model.element.VariableElement; 52 53 /** Validates the relationships between parent components and subcomponents. */ 54 final class ComponentHierarchyValidator { 55 private static final Joiner COMMA_SEPARATED_JOINER = Joiner.on(", "); 56 private final CompilerOptions compilerOptions; 57 58 @Inject ComponentHierarchyValidator(CompilerOptions compilerOptions)59 ComponentHierarchyValidator(CompilerOptions compilerOptions) { 60 this.compilerOptions = compilerOptions; 61 } 62 validate(ComponentDescriptor componentDescriptor)63 ValidationReport<TypeElement> validate(ComponentDescriptor componentDescriptor) { 64 ValidationReport.Builder<TypeElement> report = 65 ValidationReport.about(componentDescriptor.typeElement()); 66 validateSubcomponentMethods( 67 report, 68 componentDescriptor, 69 Maps.toMap(componentDescriptor.moduleTypes(), constant(componentDescriptor.typeElement()))); 70 validateRepeatedScopedDeclarations(report, componentDescriptor, LinkedHashMultimap.create()); 71 72 if (compilerOptions.scopeCycleValidationType().diagnosticKind().isPresent()) { 73 validateScopeHierarchy( 74 report, componentDescriptor, LinkedHashMultimap.<ComponentDescriptor, Scope>create()); 75 } 76 validateProductionModuleUniqueness(report, componentDescriptor, LinkedHashMultimap.create()); 77 return report.build(); 78 } 79 validateSubcomponentMethods( ValidationReport.Builder<?> report, ComponentDescriptor componentDescriptor, ImmutableMap<TypeElement, TypeElement> existingModuleToOwners)80 private void validateSubcomponentMethods( 81 ValidationReport.Builder<?> report, 82 ComponentDescriptor componentDescriptor, 83 ImmutableMap<TypeElement, TypeElement> existingModuleToOwners) { 84 componentDescriptor 85 .childComponentsDeclaredByFactoryMethods() 86 .forEach( 87 (method, childComponent) -> { 88 if (childComponent.hasCreator()) { 89 report.addError( 90 "Components may not have factory methods for subcomponents that define a " 91 + "builder.", 92 method.methodElement()); 93 } else { 94 validateFactoryMethodParameters(report, method, existingModuleToOwners); 95 } 96 97 validateSubcomponentMethods( 98 report, 99 childComponent, 100 new ImmutableMap.Builder<TypeElement, TypeElement>() 101 .putAll(existingModuleToOwners) 102 .putAll( 103 Maps.toMap( 104 Sets.difference( 105 childComponent.moduleTypes(), existingModuleToOwners.keySet()), 106 constant(childComponent.typeElement()))) 107 .build()); 108 }); 109 } 110 validateFactoryMethodParameters( ValidationReport.Builder<?> report, ComponentMethodDescriptor subcomponentMethodDescriptor, ImmutableMap<TypeElement, TypeElement> existingModuleToOwners)111 private void validateFactoryMethodParameters( 112 ValidationReport.Builder<?> report, 113 ComponentMethodDescriptor subcomponentMethodDescriptor, 114 ImmutableMap<TypeElement, TypeElement> existingModuleToOwners) { 115 for (VariableElement factoryMethodParameter : 116 subcomponentMethodDescriptor.methodElement().getParameters()) { 117 TypeElement moduleType = MoreTypes.asTypeElement(factoryMethodParameter.asType()); 118 TypeElement originatingComponent = existingModuleToOwners.get(moduleType); 119 if (originatingComponent != null) { 120 /* Factory method tries to pass a module that is already present in the parent. 121 * This is an error. */ 122 report.addError( 123 String.format( 124 "%s is present in %s. A subcomponent cannot use an instance of a " 125 + "module that differs from its parent.", 126 moduleType.getSimpleName(), originatingComponent.getQualifiedName()), 127 factoryMethodParameter); 128 } 129 } 130 } 131 132 /** 133 * Checks that components do not have any scopes that are also applied on any of their ancestors. 134 */ validateScopeHierarchy( ValidationReport.Builder<TypeElement> report, ComponentDescriptor subject, SetMultimap<ComponentDescriptor, Scope> scopesByComponent)135 private void validateScopeHierarchy( 136 ValidationReport.Builder<TypeElement> report, 137 ComponentDescriptor subject, 138 SetMultimap<ComponentDescriptor, Scope> scopesByComponent) { 139 scopesByComponent.putAll(subject, subject.scopes()); 140 141 for (ComponentDescriptor childComponent : subject.childComponents()) { 142 validateScopeHierarchy(report, childComponent, scopesByComponent); 143 } 144 145 scopesByComponent.removeAll(subject); 146 147 Predicate<Scope> subjectScopes = 148 subject.isProduction() 149 // TODO(beder): validate that @ProductionScope is only applied on production components 150 ? and(in(subject.scopes()), not(Scope::isProductionScope)) 151 : in(subject.scopes()); 152 SetMultimap<ComponentDescriptor, Scope> overlappingScopes = 153 Multimaps.filterValues(scopesByComponent, subjectScopes); 154 if (!overlappingScopes.isEmpty()) { 155 StringBuilder error = 156 new StringBuilder() 157 .append(subject.typeElement().getQualifiedName()) 158 .append(" has conflicting scopes:"); 159 for (Map.Entry<ComponentDescriptor, Scope> entry : overlappingScopes.entries()) { 160 Scope scope = entry.getValue(); 161 error 162 .append("\n ") 163 .append(entry.getKey().typeElement().getQualifiedName()) 164 .append(" also has ") 165 .append(getReadableSource(scope)); 166 } 167 report.addItem( 168 error.toString(), 169 compilerOptions.scopeCycleValidationType().diagnosticKind().get(), 170 subject.typeElement()); 171 } 172 } 173 validateProductionModuleUniqueness( ValidationReport.Builder<TypeElement> report, ComponentDescriptor componentDescriptor, SetMultimap<ComponentDescriptor, ModuleDescriptor> producerModulesByComponent)174 private void validateProductionModuleUniqueness( 175 ValidationReport.Builder<TypeElement> report, 176 ComponentDescriptor componentDescriptor, 177 SetMultimap<ComponentDescriptor, ModuleDescriptor> producerModulesByComponent) { 178 ImmutableSet<ModuleDescriptor> producerModules = 179 componentDescriptor.modules().stream() 180 .filter(module -> module.kind().equals(ModuleKind.PRODUCER_MODULE)) 181 .collect(toImmutableSet()); 182 183 producerModulesByComponent.putAll(componentDescriptor, producerModules); 184 for (ComponentDescriptor childComponent : componentDescriptor.childComponents()) { 185 validateProductionModuleUniqueness(report, childComponent, producerModulesByComponent); 186 } 187 producerModulesByComponent.removeAll(componentDescriptor); 188 189 190 SetMultimap<ComponentDescriptor, ModuleDescriptor> repeatedModules = 191 Multimaps.filterValues(producerModulesByComponent, producerModules::contains); 192 if (repeatedModules.isEmpty()) { 193 return; 194 } 195 196 StringBuilder error = new StringBuilder(); 197 Formatter formatter = new Formatter(error); 198 199 formatter.format("%s repeats @ProducerModules:", componentDescriptor.typeElement()); 200 201 for (Map.Entry<ComponentDescriptor, Collection<ModuleDescriptor>> entry : 202 repeatedModules.asMap().entrySet()) { 203 formatter.format("\n %s also installs: ", entry.getKey().typeElement()); 204 COMMA_SEPARATED_JOINER 205 .appendTo(error, Iterables.transform(entry.getValue(), m -> m.moduleElement())); 206 } 207 208 report.addError(error.toString()); 209 } 210 validateRepeatedScopedDeclarations( ValidationReport.Builder<TypeElement> report, ComponentDescriptor component, SetMultimap<ComponentDescriptor, ModuleDescriptor> modulesWithScopes)211 private void validateRepeatedScopedDeclarations( 212 ValidationReport.Builder<TypeElement> report, 213 ComponentDescriptor component, 214 // TODO(ronshapiro): optimize ModuleDescriptor.hashCode()/equals. Otherwise this could be 215 // quite costly 216 SetMultimap<ComponentDescriptor, ModuleDescriptor> modulesWithScopes) { 217 ImmutableSet<ModuleDescriptor> modules = 218 component.modules().stream().filter(this::hasScopedDeclarations).collect(toImmutableSet()); 219 modulesWithScopes.putAll(component, modules); 220 for (ComponentDescriptor childComponent : component.childComponents()) { 221 validateRepeatedScopedDeclarations(report, childComponent, modulesWithScopes); 222 } 223 modulesWithScopes.removeAll(component); 224 225 SetMultimap<ComponentDescriptor, ModuleDescriptor> repeatedModules = 226 Multimaps.filterValues(modulesWithScopes, modules::contains); 227 if (repeatedModules.isEmpty()) { 228 return; 229 } 230 231 report.addError( 232 repeatedModulesWithScopeError(component, ImmutableSetMultimap.copyOf(repeatedModules))); 233 } 234 hasScopedDeclarations(ModuleDescriptor module)235 private boolean hasScopedDeclarations(ModuleDescriptor module) { 236 return !moduleScopes(module).isEmpty(); 237 } 238 repeatedModulesWithScopeError( ComponentDescriptor component, ImmutableSetMultimap<ComponentDescriptor, ModuleDescriptor> repeatedModules)239 private String repeatedModulesWithScopeError( 240 ComponentDescriptor component, 241 ImmutableSetMultimap<ComponentDescriptor, ModuleDescriptor> repeatedModules) { 242 StringBuilder error = 243 new StringBuilder() 244 .append(component.typeElement().getQualifiedName()) 245 .append(" repeats modules with scoped bindings or declarations:"); 246 247 repeatedModules 248 .asMap() 249 .forEach( 250 (conflictingComponent, conflictingModules) -> { 251 error 252 .append("\n - ") 253 .append(conflictingComponent.typeElement().getQualifiedName()) 254 .append(" also includes:"); 255 for (ModuleDescriptor conflictingModule : conflictingModules) { 256 error 257 .append("\n - ") 258 .append(conflictingModule.moduleElement().getQualifiedName()) 259 .append(" with scopes: ") 260 .append(COMMA_SEPARATED_JOINER.join(moduleScopes(conflictingModule))); 261 } 262 }); 263 return error.toString(); 264 } 265 moduleScopes(ModuleDescriptor module)266 private ImmutableSet<Scope> moduleScopes(ModuleDescriptor module) { 267 return FluentIterable.concat(module.allBindingDeclarations()) 268 .transform(declaration -> uniqueScopeOf(declaration.bindingElement().get())) 269 .filter(scope -> scope.isPresent() && !scope.get().isReusable()) 270 .transform(scope -> scope.get()) 271 .toSet(); 272 } 273 } 274