• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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