1 /* 2 * Copyright (C) 2016 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 dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation; 20 import static dagger.internal.codegen.xprocessing.XMethodElements.getEnclosingTypeElement; 21 import static dagger.internal.codegen.xprocessing.XMethodElements.hasTypeParameters; 22 import static dagger.internal.codegen.xprocessing.XTypes.isSubtype; 23 import static java.util.stream.Collectors.joining; 24 25 import androidx.room.compiler.processing.XExecutableElement; 26 import androidx.room.compiler.processing.XMethodElement; 27 import androidx.room.compiler.processing.XProcessingEnv; 28 import androidx.room.compiler.processing.XType; 29 import androidx.room.compiler.processing.XTypeElement; 30 import androidx.room.compiler.processing.XVariableElement; 31 import com.google.common.collect.ImmutableSet; 32 import com.google.errorprone.annotations.FormatMethod; 33 import com.squareup.javapoet.ClassName; 34 import dagger.internal.codegen.binding.InjectionAnnotations; 35 import dagger.internal.codegen.javapoet.TypeNames; 36 import java.util.Optional; 37 38 /** A validator for methods that represent binding declarations. */ 39 abstract class BindingMethodValidator extends BindingElementValidator<XMethodElement> { 40 private final ClassName methodAnnotation; 41 private final ImmutableSet<ClassName> enclosingElementAnnotations; 42 private final Abstractness abstractness; 43 private final ExceptionSuperclass exceptionSuperclass; 44 private final XProcessingEnv processingEnv; 45 private final DependencyRequestValidator dependencyRequestValidator; 46 47 /** 48 * Creates a validator object. 49 * 50 * @param methodAnnotation the annotation on a method that identifies it as a binding method 51 * @param enclosingElementAnnotation the method must be declared in a class or interface annotated 52 * with this annotation 53 */ BindingMethodValidator( ClassName methodAnnotation, ClassName enclosingElementAnnotation, Abstractness abstractness, ExceptionSuperclass exceptionSuperclass, AllowsMultibindings allowsMultibindings, AllowsScoping allowsScoping, XProcessingEnv processingEnv, DependencyRequestValidator dependencyRequestValidator, InjectionAnnotations injectionAnnotations)54 protected BindingMethodValidator( 55 ClassName methodAnnotation, 56 ClassName enclosingElementAnnotation, 57 Abstractness abstractness, 58 ExceptionSuperclass exceptionSuperclass, 59 AllowsMultibindings allowsMultibindings, 60 AllowsScoping allowsScoping, 61 XProcessingEnv processingEnv, 62 DependencyRequestValidator dependencyRequestValidator, 63 InjectionAnnotations injectionAnnotations) { 64 this( 65 methodAnnotation, 66 ImmutableSet.of(enclosingElementAnnotation), 67 abstractness, 68 exceptionSuperclass, 69 allowsMultibindings, 70 allowsScoping, 71 processingEnv, 72 dependencyRequestValidator, 73 injectionAnnotations); 74 } 75 76 /** 77 * Creates a validator object. 78 * 79 * @param methodAnnotation the annotation on a method that identifies it as a binding method 80 * @param enclosingElementAnnotations the method must be declared in a class or interface 81 * annotated with one of these annotations 82 */ BindingMethodValidator( ClassName methodAnnotation, Iterable<ClassName> enclosingElementAnnotations, Abstractness abstractness, ExceptionSuperclass exceptionSuperclass, AllowsMultibindings allowsMultibindings, AllowsScoping allowsScoping, XProcessingEnv processingEnv, DependencyRequestValidator dependencyRequestValidator, InjectionAnnotations injectionAnnotations)83 protected BindingMethodValidator( 84 ClassName methodAnnotation, 85 Iterable<ClassName> enclosingElementAnnotations, 86 Abstractness abstractness, 87 ExceptionSuperclass exceptionSuperclass, 88 AllowsMultibindings allowsMultibindings, 89 AllowsScoping allowsScoping, 90 XProcessingEnv processingEnv, 91 DependencyRequestValidator dependencyRequestValidator, 92 InjectionAnnotations injectionAnnotations) { 93 super(allowsMultibindings, allowsScoping, injectionAnnotations); 94 this.methodAnnotation = methodAnnotation; 95 this.enclosingElementAnnotations = ImmutableSet.copyOf(enclosingElementAnnotations); 96 this.abstractness = abstractness; 97 this.exceptionSuperclass = exceptionSuperclass; 98 this.processingEnv = processingEnv; 99 this.dependencyRequestValidator = dependencyRequestValidator; 100 } 101 102 /** The annotation that identifies binding methods validated by this object. */ methodAnnotation()103 final ClassName methodAnnotation() { 104 return methodAnnotation; 105 } 106 107 /** 108 * Returns an error message of the form "@<i>annotation</i> methods <i>rule</i>", where 109 * <i>rule</i> comes from calling {@link String#format(String, Object...)} on {@code ruleFormat} 110 * and the other arguments. 111 */ 112 @FormatMethod bindingMethods(String ruleFormat, Object... args)113 protected final String bindingMethods(String ruleFormat, Object... args) { 114 return bindingElements(ruleFormat, args); 115 } 116 117 @Override bindingElements()118 protected final String bindingElements() { 119 return String.format("@%s methods", methodAnnotation.simpleName()); 120 } 121 122 @Override bindingElementTypeVerb()123 protected final String bindingElementTypeVerb() { 124 return "return"; 125 } 126 127 /** Abstract validator for individual binding method elements. */ 128 protected abstract class MethodValidator extends ElementValidator { 129 private final XMethodElement method; 130 MethodValidator(XMethodElement method)131 protected MethodValidator(XMethodElement method) { 132 super(method); 133 this.method = method; 134 } 135 136 @Override bindingElementType()137 protected final Optional<XType> bindingElementType() { 138 return Optional.of(method.getReturnType()); 139 } 140 141 @Override checkAdditionalProperties()142 protected final void checkAdditionalProperties() { 143 checkNotExtensionFunction(); 144 checkEnclosingElement(); 145 checkTypeParameters(); 146 checkNotPrivate(); 147 checkAbstractness(); 148 checkThrows(); 149 checkParameters(); 150 checkAdditionalMethodProperties(); 151 } 152 153 /** Checks additional properties of the binding method. */ checkAdditionalMethodProperties()154 protected void checkAdditionalMethodProperties() {} 155 checkNotExtensionFunction()156 private void checkNotExtensionFunction() { 157 if (method.isExtensionFunction()) { 158 report.addError(bindingMethods("can not be an extension function")); 159 } 160 } 161 162 /** 163 * Adds an error if the method is not declared in a class or interface annotated with one of the 164 * {@link #enclosingElementAnnotations}. 165 */ checkEnclosingElement()166 private void checkEnclosingElement() { 167 XTypeElement enclosingTypeElement = getEnclosingTypeElement(method); 168 if (enclosingTypeElement.isCompanionObject()) { 169 // Binding method is in companion object, use companion object's enclosing class instead. 170 enclosingTypeElement = enclosingTypeElement.getEnclosingTypeElement(); 171 } 172 if (!hasAnyAnnotation(enclosingTypeElement, enclosingElementAnnotations)) { 173 report.addError( 174 bindingMethods( 175 "can only be present within a @%s", 176 enclosingElementAnnotations.stream() 177 .map(ClassName::simpleName) 178 .collect(joining(" or @")))); 179 } 180 } 181 182 /** Adds an error if the method is generic. */ checkTypeParameters()183 private void checkTypeParameters() { 184 if (hasTypeParameters(method)) { 185 report.addError(bindingMethods("may not have type parameters")); 186 } 187 } 188 189 /** Adds an error if the method is private. */ checkNotPrivate()190 private void checkNotPrivate() { 191 if (method.isPrivate()) { 192 report.addError(bindingMethods("cannot be private")); 193 } 194 } 195 196 /** Adds an error if the method is abstract but must not be, or is not and must be. */ checkAbstractness()197 private void checkAbstractness() { 198 boolean isAbstract = method.isAbstract(); 199 switch (abstractness) { 200 case MUST_BE_ABSTRACT: 201 if (!isAbstract) { 202 report.addError(bindingMethods("must be abstract")); 203 } 204 break; 205 206 case MUST_BE_CONCRETE: 207 if (isAbstract) { 208 report.addError(bindingMethods("cannot be abstract")); 209 } 210 } 211 } 212 213 /** 214 * Adds an error if the method declares throws anything but an {@link Error} or an appropriate 215 * subtype of {@link Exception}. 216 */ checkThrows()217 private void checkThrows() { 218 exceptionSuperclass.checkThrows(BindingMethodValidator.this, method, report); 219 } 220 221 /** Adds errors for the method parameters. */ checkParameters()222 protected void checkParameters() { 223 for (XVariableElement parameter : method.getParameters()) { 224 checkParameter(parameter); 225 } 226 } 227 228 /** 229 * Adds errors for a method parameter. This implementation reports an error if the parameter has 230 * more than one qualifier. 231 */ checkParameter(XVariableElement parameter)232 protected void checkParameter(XVariableElement parameter) { 233 dependencyRequestValidator.validateDependencyRequest(report, parameter, parameter.getType()); 234 } 235 } 236 237 /** An abstract/concrete restriction on methods. */ 238 protected enum Abstractness { 239 MUST_BE_ABSTRACT, 240 MUST_BE_CONCRETE 241 } 242 243 /** 244 * The exception class that all {@code throws}-declared throwables must extend, other than {@link 245 * Error}. 246 */ 247 protected enum ExceptionSuperclass { 248 /** Methods may not declare any throwable types. */ 249 NO_EXCEPTIONS { 250 @Override errorMessage(BindingMethodValidator validator)251 protected String errorMessage(BindingMethodValidator validator) { 252 return validator.bindingMethods("may not throw"); 253 } 254 255 @Override checkThrows( BindingMethodValidator validator, XExecutableElement element, ValidationReport.Builder report)256 protected void checkThrows( 257 BindingMethodValidator validator, 258 XExecutableElement element, 259 ValidationReport.Builder report) { 260 if (!element.getThrownTypes().isEmpty()) { 261 report.addError(validator.bindingMethods("may not throw")); 262 return; 263 } 264 } 265 }, 266 267 /** Methods may throw checked or unchecked exceptions or errors. */ EXCEPTION(TypeNames.EXCEPTION)268 EXCEPTION(TypeNames.EXCEPTION) { 269 @Override 270 protected String errorMessage(BindingMethodValidator validator) { 271 return validator.bindingMethods( 272 "may only throw unchecked exceptions or exceptions subclassing Exception"); 273 } 274 }, 275 276 /** Methods may throw unchecked exceptions or errors. */ RUNTIME_EXCEPTION(TypeNames.RUNTIME_EXCEPTION)277 RUNTIME_EXCEPTION(TypeNames.RUNTIME_EXCEPTION) { 278 @Override 279 protected String errorMessage(BindingMethodValidator validator) { 280 return validator.bindingMethods("may only throw unchecked exceptions"); 281 } 282 }, 283 ; 284 285 @SuppressWarnings("Immutable") 286 private final ClassName superclass; 287 ExceptionSuperclass()288 ExceptionSuperclass() { 289 this(null); 290 } 291 ExceptionSuperclass(ClassName superclass)292 ExceptionSuperclass(ClassName superclass) { 293 this.superclass = superclass; 294 } 295 296 /** 297 * Adds an error if the method declares throws anything but an {@link Error} or an appropriate 298 * subtype of {@link Exception}. 299 * 300 * <p>This method is overridden in {@link #NO_EXCEPTIONS}. 301 */ checkThrows( BindingMethodValidator validator, XExecutableElement element, ValidationReport.Builder report)302 protected void checkThrows( 303 BindingMethodValidator validator, 304 XExecutableElement element, 305 ValidationReport.Builder report) { 306 XType exceptionSupertype = validator.processingEnv.findType(superclass); 307 XType errorType = validator.processingEnv.findType(TypeNames.ERROR); 308 for (XType thrownType : element.getThrownTypes()) { 309 if (!isSubtype(thrownType, exceptionSupertype) && !isSubtype(thrownType, errorType)) { 310 report.addError(errorMessage(validator)); 311 break; 312 } 313 } 314 } 315 errorMessage(BindingMethodValidator validator)316 protected abstract String errorMessage(BindingMethodValidator validator); 317 } 318 } 319