1 /*
2  * Copyright (C) 2015 Google, Inc.
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 package dagger.internal.codegen;
17 
18 import com.google.auto.common.MoreTypes;
19 import com.google.common.collect.ImmutableMap;
20 import com.google.common.collect.Maps;
21 import com.google.common.collect.Sets;
22 import dagger.internal.codegen.ComponentDescriptor.BuilderSpec;
23 import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
24 import java.util.Map;
25 import javax.lang.model.element.ExecutableElement;
26 import javax.lang.model.element.TypeElement;
27 import javax.lang.model.element.VariableElement;
28 
29 import static com.google.common.base.Functions.constant;
30 
31 /**
32  * Validates the relationships between parent components and subcomponents.
33  */
34 final class ComponentHierarchyValidator {
validate(ComponentDescriptor componentDescriptor)35   ValidationReport<TypeElement> validate(ComponentDescriptor componentDescriptor) {
36     return validateSubcomponentMethods(
37         componentDescriptor,
38         Maps.toMap(
39             componentDescriptor.transitiveModuleTypes(),
40             constant(componentDescriptor.componentDefinitionType())));
41   }
42 
validateSubcomponentMethods( ComponentDescriptor componentDescriptor, Map<TypeElement, TypeElement> existingModuleToOwners)43   private ValidationReport<TypeElement> validateSubcomponentMethods(
44       ComponentDescriptor componentDescriptor,
45       Map<TypeElement, TypeElement> existingModuleToOwners) {
46     ValidationReport.Builder<TypeElement> reportBuilder =
47         ValidationReport.about(componentDescriptor.componentDefinitionType());
48     for (Map.Entry<ComponentMethodDescriptor, ComponentDescriptor> subcomponentEntry :
49         componentDescriptor.subcomponents().entrySet()) {
50       ComponentMethodDescriptor subcomponentMethodDescriptor = subcomponentEntry.getKey();
51       ComponentDescriptor subcomponentDescriptor = subcomponentEntry.getValue();
52       // validate the way that we create subcomponents
53       switch (subcomponentMethodDescriptor.kind()) {
54         case SUBCOMPONENT:
55           for (VariableElement factoryMethodParameter :
56               subcomponentMethodDescriptor.methodElement().getParameters()) {
57             TypeElement origininatingComponent =
58                 existingModuleToOwners.get(
59                     MoreTypes.asTypeElement(factoryMethodParameter.asType()));
60             if (origininatingComponent != null) {
61               /* Factory method tries to pass a module that is already present in the parent.
62                * This is an error. */
63               reportBuilder.addError(
64                   String.format(
65                       "This module is present in %s. Subcomponents cannot use an instance of a "
66                           + "module that differs from its parent.",
67                       origininatingComponent.getQualifiedName()),
68                   factoryMethodParameter);
69             }
70           }
71           break;
72         case SUBCOMPONENT_BUILDER:
73           BuilderSpec subcomponentBuilderSpec = subcomponentDescriptor.builderSpec().get();
74           for (Map.Entry<TypeElement, ExecutableElement> builderMethodEntry :
75               subcomponentBuilderSpec.methodMap().entrySet()) {
76             TypeElement origininatingComponent =
77                 existingModuleToOwners.get(builderMethodEntry.getKey());
78             if (origininatingComponent != null) {
79               /* A subcomponent builder allows you to pass a module that is already present in the
80                * parent.  This can't be an error because it might be valid in _other_ components, so
81                * we warn here. */
82               ExecutableElement builderMethodElement = builderMethodEntry.getValue();
83               /* TODO(gak): consider putting this on the builder method directly if it's in the
84                * component being compiled */
85               reportBuilder.addWarning(
86                   String.format(
87                       "This module is present in %s. Subcomponents cannot use an instance of a "
88                           + "module that differs from its parent. The implementation of %s "
89                           + "in this component will throw %s.",
90                       origininatingComponent.getQualifiedName(),
91                       builderMethodElement.getSimpleName(),
92                       UnsupportedOperationException.class.getSimpleName()),
93                   builderMethodElement);
94             }
95           }
96           break;
97         default:
98           throw new AssertionError();
99       }
100       reportBuilder.addSubreport(
101           validateSubcomponentMethods(
102               subcomponentDescriptor,
103               new ImmutableMap.Builder<TypeElement, TypeElement>()
104                   .putAll(existingModuleToOwners)
105                   .putAll(
106                       Maps.toMap(
107                           Sets.difference(
108                               subcomponentDescriptor.transitiveModuleTypes(),
109                               existingModuleToOwners.keySet()),
110                           constant(subcomponentDescriptor.componentDefinitionType())))
111                   .build()));
112     }
113     return reportBuilder.build();
114   }
115 }
116