• 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 com.google.common.base.Verify.verifyNotNull;
20 import static dagger.internal.codegen.InjectionAnnotations.getQualifiers;
21 import static dagger.internal.codegen.MapKeys.getMapKeys;
22 import static dagger.internal.codegen.Scopes.scopesOf;
23 import static dagger.internal.codegen.Util.reentrantComputeIfAbsent;
24 import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror;
25 import static javax.lang.model.type.TypeKind.ARRAY;
26 import static javax.lang.model.type.TypeKind.DECLARED;
27 import static javax.lang.model.type.TypeKind.TYPEVAR;
28 import static javax.lang.model.type.TypeKind.VOID;
29 
30 import com.google.common.collect.ImmutableSet;
31 import com.google.errorprone.annotations.FormatMethod;
32 import dagger.MapKey;
33 import dagger.Provides;
34 import dagger.model.Key;
35 import dagger.model.Scope;
36 import dagger.multibindings.ElementsIntoSet;
37 import dagger.multibindings.IntoMap;
38 import dagger.producers.Produces;
39 import java.lang.annotation.Annotation;
40 import java.util.Formatter;
41 import java.util.HashMap;
42 import java.util.Map;
43 import java.util.Optional;
44 import javax.inject.Qualifier;
45 import javax.lang.model.element.AnnotationMirror;
46 import javax.lang.model.element.Element;
47 import javax.lang.model.element.ExecutableElement;
48 import javax.lang.model.type.TypeKind;
49 import javax.lang.model.type.TypeMirror;
50 
51 /** A validator for elements that represent binding declarations. */
52 abstract class BindingElementValidator<E extends Element> {
53   private final Class<? extends Annotation> bindingAnnotation;
54   private final AllowsMultibindings allowsMultibindings;
55   private final AllowsScoping allowsScoping;
56   private final Map<E, ValidationReport<E>> cache = new HashMap<>();
57 
58   /**
59    * Creates a validator object.
60    *
61    * @param bindingAnnotation the annotation on an element that identifies it as a binding element
62    */
BindingElementValidator( Class<? extends Annotation> bindingAnnotation, AllowsMultibindings allowsMultibindings, AllowsScoping allowsScoping)63   protected BindingElementValidator(
64       Class<? extends Annotation> bindingAnnotation,
65       AllowsMultibindings allowsMultibindings,
66       AllowsScoping allowsScoping) {
67     this.bindingAnnotation = bindingAnnotation;
68     this.allowsMultibindings = allowsMultibindings;
69     this.allowsScoping = allowsScoping;
70   }
71 
72   /** Returns a {@link ValidationReport} for {@code element}. */
validate(E element)73   final ValidationReport<E> validate(E element) {
74     return reentrantComputeIfAbsent(cache, element, this::validateUncached);
75   }
76 
validateUncached(E element)77   private ValidationReport<E> validateUncached(E element) {
78     return elementValidator(element).validate();
79   }
80 
81   /**
82    * Returns an error message of the form "&lt;{@link #bindingElements()}&gt; <i>rule</i>", where
83    * <i>rule</i> comes from calling {@link String#format(String, Object...)} on {@code ruleFormat}
84    * and the other arguments.
85    */
86   @FormatMethod
bindingElements(String ruleFormat, Object... args)87   protected final String bindingElements(String ruleFormat, Object... args) {
88     return new Formatter().format("%s ", bindingElements()).format(ruleFormat, args).toString();
89   }
90 
91   /**
92    * The kind of elements that this validator validates. Should be plural. Used for error reporting.
93    */
bindingElements()94   protected abstract String bindingElements();
95 
96   /** The verb describing the {@link ElementValidator#bindingElementType()} in error messages. */
97   // TODO(ronshapiro,dpb): improve the name of this method and it's documentation.
bindingElementTypeVerb()98   protected abstract String bindingElementTypeVerb();
99 
100   /** The error message when a binding element has a bad type. */
badTypeMessage()101   protected String badTypeMessage() {
102     return bindingElements(
103         "must %s a primitive, an array, a type variable, or a declared type",
104         bindingElementTypeVerb());
105   }
106 
107   /**
108    * The error message when a the type for a binding element with {@link
109    * ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} is a not set type.
110    */
elementsIntoSetNotASetMessage()111   protected String elementsIntoSetNotASetMessage() {
112     return bindingElements(
113         "annotated with @ElementsIntoSet must %s a Set", bindingElementTypeVerb());
114   }
115 
116   /**
117    * The error message when a the type for a binding element with {@link
118    * ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} is a raw set.
119    */
elementsIntoSetRawSetMessage()120   protected String elementsIntoSetRawSetMessage() {
121     return bindingElements(
122         "annotated with @ElementsIntoSet cannot %s a raw Set", bindingElementTypeVerb());
123   }
124 
125   /*** Returns an {@link ElementValidator} for validating the given {@code element}. */
elementValidator(E element)126   protected abstract ElementValidator elementValidator(E element);
127 
128   /** Validator for a single binding element. */
129   protected abstract class ElementValidator {
130     protected final E element;
131     protected final ValidationReport.Builder<E> report;
132 
ElementValidator(E element)133     protected ElementValidator(E element) {
134       this.element = element;
135       this.report = ValidationReport.about(element);
136     }
137 
138     /** Checks the element for validity. */
validate()139     private ValidationReport<E> validate() {
140       checkType();
141       checkQualifiers();
142       checkMapKeys();
143       checkMultibindings();
144       checkScopes();
145       checkAdditionalProperties();
146       return report.build();
147     }
148 
149     /** Check any additional properties of the element. Does nothing by default. */
checkAdditionalProperties()150     protected void checkAdditionalProperties() {}
151 
152     /**
153      * The type declared by this binding element. This may differ from a binding's {@link
154      * Key#type()}, for example in multibindings. An {@link Optional#empty()} return value indicates
155      * that the contributed type is ambiguous or missing, i.e. a {@code @BindsInstance} method with
156      * zero or many parameters.
157      */
158     // TODO(dpb): should this be an ImmutableList<TypeMirror>, with this class checking the size?
bindingElementType()159     protected abstract Optional<TypeMirror> bindingElementType();
160 
161     /**
162      * Adds an error if the {@link #bindingElementType() binding element type} is not appropriate.
163      *
164      * <p>Adds an error if the type is not a primitive, array, declared type, or type variable.
165      *
166      * <p>If the binding is not a multibinding contribution, adds an error if the type is a
167      * framework type.
168      *
169      * <p>If the element has {@link ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES}, adds an
170      * error if the type is not a {@code Set<T>} for some {@code T}
171      */
checkType()172     protected void checkType() {
173       switch (ContributionType.fromBindingElement(element)) {
174         case UNIQUE:
175           /* Validate that a unique binding is not attempting to bind a framework type. This
176            * validation is only appropriate for unique bindings because multibindings may collect
177            * framework types.  E.g. Set<Provider<Foo>> is perfectly reasonable. */
178           checkFrameworkType();
179           // fall through
180 
181         case SET:
182         case MAP:
183           bindingElementType().ifPresent(type -> checkKeyType(type));
184           break;
185 
186         case SET_VALUES:
187           checkSetValuesType();
188       }
189     }
190 
191     /**
192      * Adds an error if {@code keyType} is not a primitive, declared type, array, or type variable.
193      */
checkKeyType(TypeMirror keyType)194     protected void checkKeyType(TypeMirror keyType) {
195       TypeKind kind = keyType.getKind();
196       if (kind.equals(VOID)) {
197         report.addError(bindingElements("must %s a value (not void)", bindingElementTypeVerb()));
198       } else if (!(kind.isPrimitive()
199           || kind.equals(DECLARED)
200           || kind.equals(ARRAY)
201           || kind.equals(TYPEVAR))) {
202         report.addError(badTypeMessage());
203       }
204     }
205 
206     /**
207      * Adds an error if the type for an element with {@link ElementsIntoSet @ElementsIntoSet} or
208      * {@code SET_VALUES} is not a a {@code Set<T>} for a reasonable {@code T}.
209      */
210     // TODO(gak): should we allow "covariant return" for set values?
checkSetValuesType()211     protected void checkSetValuesType() {
212       bindingElementType().ifPresent(keyType -> checkSetValuesType(keyType));
213     }
214 
215     /** Adds an error if {@code type} is not a {@code Set<T>} for a reasonable {@code T}. */
checkSetValuesType(TypeMirror type)216     protected final void checkSetValuesType(TypeMirror type) {
217       if (!SetType.isSet(type)) {
218         report.addError(elementsIntoSetNotASetMessage());
219       } else {
220         SetType setType = SetType.from(type);
221         if (setType.isRawType()) {
222           report.addError(elementsIntoSetRawSetMessage());
223         } else {
224           checkKeyType(setType.elementType());
225         }
226       }
227     }
228 
229     /**
230      * Adds an error if the element has more than one {@linkplain Qualifier qualifier} annotation.
231      */
checkQualifiers()232     private void checkQualifiers() {
233       ImmutableSet<? extends AnnotationMirror> qualifiers = getQualifiers(element);
234       if (qualifiers.size() > 1) {
235         for (AnnotationMirror qualifier : qualifiers) {
236           report.addError(
237               bindingElements("may not use more than one @Qualifier"),
238               element,
239               qualifier);
240         }
241       }
242     }
243 
244     /**
245      * Adds an error if an {@link IntoMap @IntoMap} element doesn't have exactly one {@link
246      * MapKey @MapKey} annotation, or if an element that is {@link IntoMap @IntoMap} has any.
247      */
checkMapKeys()248     private void checkMapKeys() {
249       if (!allowsMultibindings.allowsMultibindings()) {
250         return;
251       }
252       ImmutableSet<? extends AnnotationMirror> mapKeys = getMapKeys(element);
253       if (ContributionType.fromBindingElement(element).equals(ContributionType.MAP)) {
254         switch (mapKeys.size()) {
255           case 0:
256             report.addError(bindingElements("of type map must declare a map key"));
257             break;
258           case 1:
259             break;
260           default:
261             report.addError(bindingElements("may not have more than one map key"));
262             break;
263         }
264       } else if (!mapKeys.isEmpty()) {
265         report.addError(bindingElements("of non map type cannot declare a map key"));
266       }
267     }
268 
269     /**
270      * Adds errors if:
271      *
272      * <ul>
273      *   <li>the element doesn't allow {@linkplain MultibindingAnnotations multibinding annotations}
274      *       and has any
275      *   <li>the element does allow them but has more than one
276      *   <li>the element has a multibinding annotation and its {@link Provides} or {@link Produces}
277      *       annotation has a {@code type} parameter.
278      * </ul>
279      */
checkMultibindings()280     private void checkMultibindings() {
281       ImmutableSet<AnnotationMirror> multibindingAnnotations =
282           MultibindingAnnotations.forElement(element);
283 
284       switch (allowsMultibindings) {
285         case NO_MULTIBINDINGS:
286           for (AnnotationMirror annotation : multibindingAnnotations) {
287             report.addError(
288                 bindingElements("cannot have multibinding annotations"),
289                 element,
290                 annotation);
291           }
292           break;
293 
294         case ALLOWS_MULTIBINDINGS:
295           if (multibindingAnnotations.size() > 1) {
296             for (AnnotationMirror annotation : multibindingAnnotations) {
297               report.addError(
298                   bindingElements("cannot have more than one multibinding annotation"),
299                   element,
300                   annotation);
301             }
302           }
303           break;
304       }
305 
306       // TODO(ronshapiro): move this into ProvidesMethodValidator
307       if (bindingAnnotation.equals(Provides.class)) {
308         AnnotationMirror bindingAnnotationMirror =
309             getAnnotationMirror(element, bindingAnnotation).get();
310         boolean usesProvidesType = false;
311         for (ExecutableElement member : bindingAnnotationMirror.getElementValues().keySet()) {
312           usesProvidesType |= member.getSimpleName().contentEquals("type");
313         }
314         if (usesProvidesType && !multibindingAnnotations.isEmpty()) {
315           report.addError(
316               "@Provides.type cannot be used with multibinding annotations", element);
317         }
318       }
319     }
320 
321     /**
322      * Adds an error if the element has a scope but doesn't allow scoping, or if it has more than
323      * one {@linkplain Scope scope} annotation.
324      */
checkScopes()325     private void checkScopes() {
326       ImmutableSet<Scope> scopes = scopesOf(element);
327       String error = null;
328       switch (allowsScoping) {
329         case ALLOWS_SCOPING:
330           if (scopes.size() <= 1) {
331             return;
332           }
333           error = bindingElements("cannot use more than one @Scope");
334           break;
335         case NO_SCOPING:
336           error = bindingElements("cannot be scoped");
337           break;
338       }
339       verifyNotNull(error);
340       for (Scope scope : scopes) {
341         report.addError(error, element, scope.scopeAnnotation());
342       }
343     }
344 
345     /**
346      * Adds an error if the {@link #bindingElementType() type} is a {@linkplain FrameworkTypes
347      * framework type}.
348      */
checkFrameworkType()349     private void checkFrameworkType() {
350       if (bindingElementType().filter(FrameworkTypes::isFrameworkType).isPresent()) {
351         report.addError(bindingElements("must not %s framework types", bindingElementTypeVerb()));
352       }
353     }
354   }
355 
356   /** Whether to check multibinding annotations. */
357   enum AllowsMultibindings {
358     /**
359      * This element disallows multibinding annotations, so don't bother checking for their validity.
360      * {@link MultibindingAnnotationsProcessingStep} will add errors if the element has any
361      * multibinding annotations.
362      */
363     NO_MULTIBINDINGS,
364 
365     /** This element allows multibinding annotations, so validate them. */
366     ALLOWS_MULTIBINDINGS,
367     ;
368 
allowsMultibindings()369     private boolean allowsMultibindings() {
370       return this == ALLOWS_MULTIBINDINGS;
371     }
372   }
373 
374   /** How to check scoping annotations. */
375   enum AllowsScoping {
376     /** This element disallows scoping, so check that no scope annotations are present. */
377     NO_SCOPING,
378 
379     /** This element allows scoping, so validate that there's at most one scope annotation. */
380     ALLOWS_SCOPING,
381     ;
382   }
383 }
384