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.auto.common.MoreElements.isAnnotationPresent; 20 import static com.google.common.collect.Iterables.getOnlyElement; 21 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; 22 import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.getCreatorAnnotations; 23 import static javax.lang.model.element.Modifier.ABSTRACT; 24 import static javax.lang.model.element.Modifier.PRIVATE; 25 import static javax.lang.model.element.Modifier.STATIC; 26 import static javax.lang.model.util.ElementFilter.methodsIn; 27 28 import com.google.auto.common.MoreElements; 29 import com.google.common.collect.ImmutableList; 30 import com.google.common.collect.ImmutableSet; 31 import com.google.common.collect.ObjectArrays; 32 import dagger.BindsInstance; 33 import dagger.internal.codegen.base.ClearableCache; 34 import dagger.internal.codegen.binding.ComponentCreatorAnnotation; 35 import dagger.internal.codegen.binding.ErrorMessages; 36 import dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages; 37 import dagger.internal.codegen.langmodel.DaggerElements; 38 import dagger.internal.codegen.langmodel.DaggerTypes; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 import javax.inject.Inject; 44 import javax.inject.Singleton; 45 import javax.lang.model.element.Element; 46 import javax.lang.model.element.ExecutableElement; 47 import javax.lang.model.element.Modifier; 48 import javax.lang.model.element.TypeElement; 49 import javax.lang.model.element.VariableElement; 50 import javax.lang.model.type.TypeKind; 51 import javax.lang.model.type.TypeMirror; 52 import javax.lang.model.util.ElementFilter; 53 54 /** Validates types annotated with component creator annotations. */ 55 @Singleton 56 public final class ComponentCreatorValidator implements ClearableCache { 57 58 private final DaggerElements elements; 59 private final DaggerTypes types; 60 private final Map<TypeElement, ValidationReport<TypeElement>> reports = new HashMap<>(); 61 62 @Inject ComponentCreatorValidator(DaggerElements elements, DaggerTypes types)63 ComponentCreatorValidator(DaggerElements elements, DaggerTypes types) { 64 this.elements = elements; 65 this.types = types; 66 } 67 68 @Override clearCache()69 public void clearCache() { 70 reports.clear(); 71 } 72 73 /** Validates that the given {@code type} is potentially a valid component creator type. */ validate(TypeElement type)74 public ValidationReport<TypeElement> validate(TypeElement type) { 75 return reentrantComputeIfAbsent(reports, type, this::validateUncached); 76 } 77 validateUncached(TypeElement type)78 private ValidationReport<TypeElement> validateUncached(TypeElement type) { 79 ValidationReport.Builder<TypeElement> report = ValidationReport.about(type); 80 81 ImmutableSet<ComponentCreatorAnnotation> creatorAnnotations = getCreatorAnnotations(type); 82 if (!validateOnlyOneCreatorAnnotation(creatorAnnotations, report)) { 83 return report.build(); 84 } 85 86 // Note: there's more validation in ComponentDescriptorValidator: 87 // - to make sure the setter methods/factory parameters mirror the deps 88 // - to make sure each type or key is set by only one method or parameter 89 ElementValidator validator = 90 new ElementValidator(type, report, getOnlyElement(creatorAnnotations)); 91 return validator.validate(); 92 } 93 validateOnlyOneCreatorAnnotation( ImmutableSet<ComponentCreatorAnnotation> creatorAnnotations, ValidationReport.Builder<?> report)94 private boolean validateOnlyOneCreatorAnnotation( 95 ImmutableSet<ComponentCreatorAnnotation> creatorAnnotations, 96 ValidationReport.Builder<?> report) { 97 // creatorAnnotations should never be empty because this should only ever be called for 98 // types that have been found to have some creator annotation 99 if (creatorAnnotations.size() > 1) { 100 String error = 101 "May not have more than one component Factory or Builder annotation on a type" 102 + ": found " 103 + creatorAnnotations; 104 report.addError(error); 105 return false; 106 } 107 108 return true; 109 } 110 111 /** 112 * Validator for a single {@link TypeElement} that is annotated with a {@code Builder} or {@code 113 * Factory} annotation. 114 */ 115 private final class ElementValidator { 116 private final TypeElement type; 117 private final Element component; 118 private final ValidationReport.Builder<TypeElement> report; 119 private final ComponentCreatorAnnotation annotation; 120 private final ComponentCreatorMessages messages; 121 ElementValidator( TypeElement type, ValidationReport.Builder<TypeElement> report, ComponentCreatorAnnotation annotation)122 private ElementValidator( 123 TypeElement type, 124 ValidationReport.Builder<TypeElement> report, 125 ComponentCreatorAnnotation annotation) { 126 this.type = type; 127 this.component = type.getEnclosingElement(); 128 this.report = report; 129 this.annotation = annotation; 130 this.messages = ErrorMessages.creatorMessagesFor(annotation); 131 } 132 133 /** Validates the creator type. */ validate()134 final ValidationReport<TypeElement> validate() { 135 if (!isAnnotationPresent(component, annotation.componentAnnotation())) { 136 report.addError(messages.mustBeInComponent()); 137 } 138 139 // If the type isn't a class or interface, don't validate anything else since the rest of the 140 // messages will be bogus. 141 if (!validateIsClassOrInterface()) { 142 return report.build(); 143 } 144 145 validateTypeRequirements(); 146 switch (annotation.creatorKind()) { 147 case FACTORY: 148 validateFactory(); 149 break; 150 case BUILDER: 151 validateBuilder(); 152 } 153 154 return report.build(); 155 } 156 157 /** Validates that the type is a class or interface type and returns true if it is. */ validateIsClassOrInterface()158 private boolean validateIsClassOrInterface() { 159 switch (type.getKind()) { 160 case CLASS: 161 validateConstructor(); 162 return true; 163 case INTERFACE: 164 return true; 165 default: 166 report.addError(messages.mustBeClassOrInterface()); 167 } 168 return false; 169 } 170 validateConstructor()171 private void validateConstructor() { 172 List<? extends Element> allElements = type.getEnclosedElements(); 173 List<ExecutableElement> constructors = ElementFilter.constructorsIn(allElements); 174 175 boolean valid = true; 176 if (constructors.size() != 1) { 177 valid = false; 178 } else { 179 ExecutableElement constructor = getOnlyElement(constructors); 180 valid = 181 constructor.getParameters().isEmpty() && !constructor.getModifiers().contains(PRIVATE); 182 } 183 184 if (!valid) { 185 report.addError(messages.invalidConstructor()); 186 } 187 } 188 189 /** Validates basic requirements about the type that are common to both creator kinds. */ validateTypeRequirements()190 private void validateTypeRequirements() { 191 if (!type.getTypeParameters().isEmpty()) { 192 report.addError(messages.generics()); 193 } 194 195 Set<Modifier> modifiers = type.getModifiers(); 196 if (modifiers.contains(PRIVATE)) { 197 report.addError(messages.isPrivate()); 198 } 199 if (!modifiers.contains(STATIC)) { 200 report.addError(messages.mustBeStatic()); 201 } 202 // Note: Must be abstract, so no need to check for final. 203 if (!modifiers.contains(ABSTRACT)) { 204 report.addError(messages.mustBeAbstract()); 205 } 206 } 207 validateBuilder()208 private void validateBuilder() { 209 ExecutableElement buildMethod = null; 210 for (ExecutableElement method : elements.getUnimplementedMethods(type)) { 211 switch (method.getParameters().size()) { 212 case 0: // If this is potentially a build() method, validate it returns the correct type. 213 if (validateFactoryMethodReturnType(method)) { 214 if (buildMethod != null) { 215 // If we found more than one build-like method, fail. 216 error( 217 method, 218 messages.twoFactoryMethods(), 219 messages.inheritedTwoFactoryMethods(), 220 buildMethod); 221 } 222 } 223 // We set the buildMethod regardless of the return type to reduce error spam. 224 buildMethod = method; 225 break; 226 227 case 1: // If this correctly had one parameter, make sure the return types are valid. 228 validateSetterMethod(method); 229 break; 230 231 default: // more than one parameter 232 error( 233 method, 234 messages.setterMethodsMustTakeOneArg(), 235 messages.inheritedSetterMethodsMustTakeOneArg()); 236 break; 237 } 238 } 239 240 if (buildMethod == null) { 241 report.addError(messages.missingFactoryMethod()); 242 } else { 243 validateNotGeneric(buildMethod); 244 } 245 } 246 validateSetterMethod(ExecutableElement method)247 private void validateSetterMethod(ExecutableElement method) { 248 TypeMirror returnType = types.resolveExecutableType(method, type.asType()).getReturnType(); 249 if (returnType.getKind() != TypeKind.VOID && !types.isSubtype(type.asType(), returnType)) { 250 error( 251 method, 252 messages.setterMethodsMustReturnVoidOrBuilder(), 253 messages.inheritedSetterMethodsMustReturnVoidOrBuilder()); 254 } 255 256 validateNotGeneric(method); 257 258 VariableElement parameter = method.getParameters().get(0); 259 260 boolean methodIsBindsInstance = isAnnotationPresent(method, BindsInstance.class); 261 boolean parameterIsBindsInstance = isAnnotationPresent(parameter, BindsInstance.class); 262 boolean bindsInstance = methodIsBindsInstance || parameterIsBindsInstance; 263 264 if (methodIsBindsInstance && parameterIsBindsInstance) { 265 error( 266 method, 267 messages.bindsInstanceNotAllowedOnBothSetterMethodAndParameter(), 268 messages.inheritedBindsInstanceNotAllowedOnBothSetterMethodAndParameter()); 269 } 270 271 if (!bindsInstance && parameter.asType().getKind().isPrimitive()) { 272 error( 273 method, 274 messages.nonBindsInstanceParametersMayNotBePrimitives(), 275 messages.inheritedNonBindsInstanceParametersMayNotBePrimitives()); 276 } 277 } 278 validateFactory()279 private void validateFactory() { 280 ImmutableList<ExecutableElement> abstractMethods = 281 elements.getUnimplementedMethods(type).asList(); 282 switch (abstractMethods.size()) { 283 case 0: 284 report.addError(messages.missingFactoryMethod()); 285 return; 286 case 1: 287 break; // good 288 default: 289 error( 290 abstractMethods.get(1), 291 messages.twoFactoryMethods(), 292 messages.inheritedTwoFactoryMethods(), 293 abstractMethods.get(0)); 294 return; 295 } 296 297 validateFactoryMethod(getOnlyElement(abstractMethods)); 298 } 299 300 /** Validates that the given {@code method} is a valid component factory method. */ validateFactoryMethod(ExecutableElement method)301 private void validateFactoryMethod(ExecutableElement method) { 302 validateNotGeneric(method); 303 304 if (!validateFactoryMethodReturnType(method)) { 305 // If we can't determine that the single method is a valid factory method, don't bother 306 // validating its parameters. 307 return; 308 } 309 310 for (VariableElement parameter : method.getParameters()) { 311 if (!isAnnotationPresent(parameter, BindsInstance.class) 312 && parameter.asType().getKind().isPrimitive()) { 313 error( 314 method, 315 messages.nonBindsInstanceParametersMayNotBePrimitives(), 316 messages.inheritedNonBindsInstanceParametersMayNotBePrimitives()); 317 } 318 } 319 } 320 321 /** 322 * Validates that the factory method that actually returns a new component instance. Returns 323 * true if the return type was valid. 324 */ validateFactoryMethodReturnType(ExecutableElement method)325 private boolean validateFactoryMethodReturnType(ExecutableElement method) { 326 TypeMirror returnType = types.resolveExecutableType(method, type.asType()).getReturnType(); 327 328 if (!types.isSubtype(component.asType(), returnType)) { 329 error( 330 method, 331 messages.factoryMethodMustReturnComponentType(), 332 messages.inheritedFactoryMethodMustReturnComponentType()); 333 return false; 334 } 335 336 if (isAnnotationPresent(method, BindsInstance.class)) { 337 error( 338 method, 339 messages.factoryMethodMayNotBeAnnotatedWithBindsInstance(), 340 messages.inheritedFactoryMethodMayNotBeAnnotatedWithBindsInstance()); 341 return false; 342 } 343 344 TypeElement componentType = MoreElements.asType(component); 345 if (!types.isSameType(componentType.asType(), returnType)) { 346 ImmutableSet<ExecutableElement> methodsOnlyInComponent = 347 methodsOnlyInComponent(componentType); 348 if (!methodsOnlyInComponent.isEmpty()) { 349 report.addWarning( 350 messages.factoryMethodReturnsSupertypeWithMissingMethods( 351 componentType, type, returnType, method, methodsOnlyInComponent), 352 method); 353 } 354 } 355 return true; 356 } 357 358 /** 359 * Generates one of two error messages. If the method is enclosed in the subject, we target the 360 * error to the method itself. Otherwise we target the error to the subject and list the method 361 * as an argument. (Otherwise we have no way of knowing if the method is being compiled in this 362 * pass too, so javac might not be able to pinpoint it's line of code.) 363 */ 364 /* 365 * For Component.Builder, the prototypical example would be if someone had: 366 * libfoo: interface SharedBuilder { void badSetter(A a, B b); } 367 * libbar: BarComponent { BarBuilder extends SharedBuilder } } 368 * ... the compiler only validates BarBuilder when compiling libbar, but it fails because 369 * of libfoo's SharedBuilder (which could have been compiled in a previous pass). 370 * So we can't point to SharedBuilder#badSetter as the subject of the BarBuilder validation 371 * failure. 372 * 373 * This check is a little more strict than necessary -- ideally we'd check if method's enclosing 374 * class was included in this compile run. But that's hard, and this is close enough. 375 */ error( ExecutableElement method, String enclosedError, String inheritedError, Object... extraArgs)376 private void error( 377 ExecutableElement method, 378 String enclosedError, 379 String inheritedError, 380 Object... extraArgs) { 381 if (method.getEnclosingElement().equals(type)) { 382 report.addError(String.format(enclosedError, extraArgs), method); 383 } else { 384 report.addError(String.format(inheritedError, ObjectArrays.concat(extraArgs, method))); 385 } 386 } 387 388 /** Validates that the given {@code method} is not generic. * */ validateNotGeneric(ExecutableElement method)389 private void validateNotGeneric(ExecutableElement method) { 390 if (!method.getTypeParameters().isEmpty()) { 391 error( 392 method, 393 messages.methodsMayNotHaveTypeParameters(), 394 messages.inheritedMethodsMayNotHaveTypeParameters()); 395 } 396 } 397 398 /** 399 * Returns all methods defind in {@code componentType} which are not inherited from a supertype. 400 */ methodsOnlyInComponent(TypeElement componentType)401 private ImmutableSet<ExecutableElement> methodsOnlyInComponent(TypeElement componentType) { 402 // TODO(ronshapiro): Ideally this shouldn't return methods which are redeclared from a 403 // supertype, but do not change the return type. We don't have a good/simple way of checking 404 // that, and it doesn't seem likely, so the warning won't be too bad. 405 return ImmutableSet.copyOf(methodsIn(componentType.getEnclosedElements())); 406 } 407 } 408 } 409