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