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