• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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