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