• 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 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