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