• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.isAnnotationPresent;
20 import static com.google.common.collect.Iterables.getOnlyElement;
21 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
22 import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.getCreatorAnnotations;
23 import static javax.lang.model.element.Modifier.ABSTRACT;
24 import static javax.lang.model.element.Modifier.PRIVATE;
25 import static javax.lang.model.element.Modifier.STATIC;
26 import static javax.lang.model.util.ElementFilter.methodsIn;
27 
28 import com.google.auto.common.MoreElements;
29 import com.google.common.collect.ImmutableList;
30 import com.google.common.collect.ImmutableSet;
31 import com.google.common.collect.ObjectArrays;
32 import dagger.BindsInstance;
33 import dagger.internal.codegen.base.ClearableCache;
34 import dagger.internal.codegen.binding.ComponentCreatorAnnotation;
35 import dagger.internal.codegen.binding.ErrorMessages;
36 import dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages;
37 import dagger.internal.codegen.langmodel.DaggerElements;
38 import dagger.internal.codegen.langmodel.DaggerTypes;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 import javax.inject.Inject;
44 import javax.inject.Singleton;
45 import javax.lang.model.element.Element;
46 import javax.lang.model.element.ExecutableElement;
47 import javax.lang.model.element.Modifier;
48 import javax.lang.model.element.TypeElement;
49 import javax.lang.model.element.VariableElement;
50 import javax.lang.model.type.TypeKind;
51 import javax.lang.model.type.TypeMirror;
52 import javax.lang.model.util.ElementFilter;
53 
54 /** Validates types annotated with component creator annotations. */
55 @Singleton
56 public final class ComponentCreatorValidator implements ClearableCache {
57 
58   private final DaggerElements elements;
59   private final DaggerTypes types;
60   private final Map<TypeElement, ValidationReport<TypeElement>> reports = new HashMap<>();
61 
62   @Inject
ComponentCreatorValidator(DaggerElements elements, DaggerTypes types)63   ComponentCreatorValidator(DaggerElements elements, DaggerTypes types) {
64     this.elements = elements;
65     this.types = types;
66   }
67 
68   @Override
clearCache()69   public void clearCache() {
70     reports.clear();
71   }
72 
73   /** Validates that the given {@code type} is potentially a valid component creator type. */
validate(TypeElement type)74   public ValidationReport<TypeElement> validate(TypeElement type) {
75     return reentrantComputeIfAbsent(reports, type, this::validateUncached);
76   }
77 
validateUncached(TypeElement type)78   private ValidationReport<TypeElement> validateUncached(TypeElement type) {
79     ValidationReport.Builder<TypeElement> report = ValidationReport.about(type);
80 
81     ImmutableSet<ComponentCreatorAnnotation> creatorAnnotations = getCreatorAnnotations(type);
82     if (!validateOnlyOneCreatorAnnotation(creatorAnnotations, report)) {
83       return report.build();
84     }
85 
86     // Note: there's more validation in ComponentDescriptorValidator:
87     // - to make sure the setter methods/factory parameters mirror the deps
88     // - to make sure each type or key is set by only one method or parameter
89     ElementValidator validator =
90         new ElementValidator(type, report, getOnlyElement(creatorAnnotations));
91     return validator.validate();
92   }
93 
validateOnlyOneCreatorAnnotation( ImmutableSet<ComponentCreatorAnnotation> creatorAnnotations, ValidationReport.Builder<?> report)94   private boolean validateOnlyOneCreatorAnnotation(
95       ImmutableSet<ComponentCreatorAnnotation> creatorAnnotations,
96       ValidationReport.Builder<?> report) {
97     // creatorAnnotations should never be empty because this should only ever be called for
98     // types that have been found to have some creator annotation
99     if (creatorAnnotations.size() > 1) {
100       String error =
101           "May not have more than one component Factory or Builder annotation on a type"
102               + ": found "
103               + creatorAnnotations;
104       report.addError(error);
105       return false;
106     }
107 
108     return true;
109   }
110 
111   /**
112    * Validator for a single {@link TypeElement} that is annotated with a {@code Builder} or {@code
113    * Factory} annotation.
114    */
115   private final class ElementValidator {
116     private final TypeElement type;
117     private final Element component;
118     private final ValidationReport.Builder<TypeElement> report;
119     private final ComponentCreatorAnnotation annotation;
120     private final ComponentCreatorMessages messages;
121 
ElementValidator( TypeElement type, ValidationReport.Builder<TypeElement> report, ComponentCreatorAnnotation annotation)122     private ElementValidator(
123         TypeElement type,
124         ValidationReport.Builder<TypeElement> report,
125         ComponentCreatorAnnotation annotation) {
126       this.type = type;
127       this.component = type.getEnclosingElement();
128       this.report = report;
129       this.annotation = annotation;
130       this.messages = ErrorMessages.creatorMessagesFor(annotation);
131     }
132 
133     /** Validates the creator type. */
validate()134     final ValidationReport<TypeElement> validate() {
135       if (!isAnnotationPresent(component, annotation.componentAnnotation())) {
136         report.addError(messages.mustBeInComponent());
137       }
138 
139       // If the type isn't a class or interface, don't validate anything else since the rest of the
140       // messages will be bogus.
141       if (!validateIsClassOrInterface()) {
142         return report.build();
143       }
144 
145       validateTypeRequirements();
146       switch (annotation.creatorKind()) {
147         case FACTORY:
148           validateFactory();
149           break;
150         case BUILDER:
151           validateBuilder();
152       }
153 
154       return report.build();
155     }
156 
157     /** Validates that the type is a class or interface type and returns true if it is. */
validateIsClassOrInterface()158     private boolean validateIsClassOrInterface() {
159       switch (type.getKind()) {
160         case CLASS:
161           validateConstructor();
162           return true;
163         case INTERFACE:
164           return true;
165         default:
166           report.addError(messages.mustBeClassOrInterface());
167       }
168       return false;
169     }
170 
validateConstructor()171     private void validateConstructor() {
172       List<? extends Element> allElements = type.getEnclosedElements();
173       List<ExecutableElement> constructors = ElementFilter.constructorsIn(allElements);
174 
175       boolean valid = true;
176       if (constructors.size() != 1) {
177         valid = false;
178       } else {
179         ExecutableElement constructor = getOnlyElement(constructors);
180         valid =
181             constructor.getParameters().isEmpty() && !constructor.getModifiers().contains(PRIVATE);
182       }
183 
184       if (!valid) {
185         report.addError(messages.invalidConstructor());
186       }
187     }
188 
189     /** Validates basic requirements about the type that are common to both creator kinds. */
validateTypeRequirements()190     private void validateTypeRequirements() {
191       if (!type.getTypeParameters().isEmpty()) {
192         report.addError(messages.generics());
193       }
194 
195       Set<Modifier> modifiers = type.getModifiers();
196       if (modifiers.contains(PRIVATE)) {
197         report.addError(messages.isPrivate());
198       }
199       if (!modifiers.contains(STATIC)) {
200         report.addError(messages.mustBeStatic());
201       }
202       // Note: Must be abstract, so no need to check for final.
203       if (!modifiers.contains(ABSTRACT)) {
204         report.addError(messages.mustBeAbstract());
205       }
206     }
207 
validateBuilder()208     private void validateBuilder() {
209       ExecutableElement buildMethod = null;
210       for (ExecutableElement method : elements.getUnimplementedMethods(type)) {
211         switch (method.getParameters().size()) {
212           case 0: // If this is potentially a build() method, validate it returns the correct type.
213             if (validateFactoryMethodReturnType(method)) {
214               if (buildMethod != null) {
215                 // If we found more than one build-like method, fail.
216                 error(
217                     method,
218                     messages.twoFactoryMethods(),
219                     messages.inheritedTwoFactoryMethods(),
220                     buildMethod);
221               }
222             }
223             // We set the buildMethod regardless of the return type to reduce error spam.
224             buildMethod = method;
225             break;
226 
227           case 1: // If this correctly had one parameter, make sure the return types are valid.
228             validateSetterMethod(method);
229             break;
230 
231           default: // more than one parameter
232             error(
233                 method,
234                 messages.setterMethodsMustTakeOneArg(),
235                 messages.inheritedSetterMethodsMustTakeOneArg());
236             break;
237         }
238       }
239 
240       if (buildMethod == null) {
241         report.addError(messages.missingFactoryMethod());
242       } else {
243         validateNotGeneric(buildMethod);
244       }
245     }
246 
validateSetterMethod(ExecutableElement method)247     private void validateSetterMethod(ExecutableElement method) {
248       TypeMirror returnType = types.resolveExecutableType(method, type.asType()).getReturnType();
249       if (returnType.getKind() != TypeKind.VOID && !types.isSubtype(type.asType(), returnType)) {
250         error(
251             method,
252             messages.setterMethodsMustReturnVoidOrBuilder(),
253             messages.inheritedSetterMethodsMustReturnVoidOrBuilder());
254       }
255 
256       validateNotGeneric(method);
257 
258       VariableElement parameter = method.getParameters().get(0);
259 
260       boolean methodIsBindsInstance = isAnnotationPresent(method, BindsInstance.class);
261       boolean parameterIsBindsInstance = isAnnotationPresent(parameter, BindsInstance.class);
262       boolean bindsInstance = methodIsBindsInstance || parameterIsBindsInstance;
263 
264       if (methodIsBindsInstance && parameterIsBindsInstance) {
265         error(
266             method,
267             messages.bindsInstanceNotAllowedOnBothSetterMethodAndParameter(),
268             messages.inheritedBindsInstanceNotAllowedOnBothSetterMethodAndParameter());
269       }
270 
271       if (!bindsInstance && parameter.asType().getKind().isPrimitive()) {
272         error(
273             method,
274             messages.nonBindsInstanceParametersMayNotBePrimitives(),
275             messages.inheritedNonBindsInstanceParametersMayNotBePrimitives());
276       }
277     }
278 
validateFactory()279     private void validateFactory() {
280       ImmutableList<ExecutableElement> abstractMethods =
281           elements.getUnimplementedMethods(type).asList();
282       switch (abstractMethods.size()) {
283         case 0:
284           report.addError(messages.missingFactoryMethod());
285           return;
286         case 1:
287           break; // good
288         default:
289           error(
290               abstractMethods.get(1),
291               messages.twoFactoryMethods(),
292               messages.inheritedTwoFactoryMethods(),
293               abstractMethods.get(0));
294           return;
295       }
296 
297       validateFactoryMethod(getOnlyElement(abstractMethods));
298     }
299 
300     /** Validates that the given {@code method} is a valid component factory method. */
validateFactoryMethod(ExecutableElement method)301     private void validateFactoryMethod(ExecutableElement method) {
302       validateNotGeneric(method);
303 
304       if (!validateFactoryMethodReturnType(method)) {
305         // If we can't determine that the single method is a valid factory method, don't bother
306         // validating its parameters.
307         return;
308       }
309 
310       for (VariableElement parameter : method.getParameters()) {
311         if (!isAnnotationPresent(parameter, BindsInstance.class)
312             && parameter.asType().getKind().isPrimitive()) {
313           error(
314               method,
315               messages.nonBindsInstanceParametersMayNotBePrimitives(),
316               messages.inheritedNonBindsInstanceParametersMayNotBePrimitives());
317         }
318       }
319     }
320 
321     /**
322      * Validates that the factory method that actually returns a new component instance. Returns
323      * true if the return type was valid.
324      */
validateFactoryMethodReturnType(ExecutableElement method)325     private boolean validateFactoryMethodReturnType(ExecutableElement method) {
326       TypeMirror returnType = types.resolveExecutableType(method, type.asType()).getReturnType();
327 
328       if (!types.isSubtype(component.asType(), returnType)) {
329         error(
330             method,
331             messages.factoryMethodMustReturnComponentType(),
332             messages.inheritedFactoryMethodMustReturnComponentType());
333         return false;
334       }
335 
336       if (isAnnotationPresent(method, BindsInstance.class)) {
337         error(
338             method,
339             messages.factoryMethodMayNotBeAnnotatedWithBindsInstance(),
340             messages.inheritedFactoryMethodMayNotBeAnnotatedWithBindsInstance());
341         return false;
342       }
343 
344       TypeElement componentType = MoreElements.asType(component);
345       if (!types.isSameType(componentType.asType(), returnType)) {
346         ImmutableSet<ExecutableElement> methodsOnlyInComponent =
347             methodsOnlyInComponent(componentType);
348         if (!methodsOnlyInComponent.isEmpty()) {
349           report.addWarning(
350               messages.factoryMethodReturnsSupertypeWithMissingMethods(
351                   componentType, type, returnType, method, methodsOnlyInComponent),
352               method);
353         }
354       }
355       return true;
356     }
357 
358     /**
359      * Generates one of two error messages. If the method is enclosed in the subject, we target the
360      * error to the method itself. Otherwise we target the error to the subject and list the method
361      * as an argument. (Otherwise we have no way of knowing if the method is being compiled in this
362      * pass too, so javac might not be able to pinpoint it's line of code.)
363      */
364     /*
365      * For Component.Builder, the prototypical example would be if someone had:
366      *    libfoo: interface SharedBuilder { void badSetter(A a, B b); }
367      *    libbar: BarComponent { BarBuilder extends SharedBuilder } }
368      * ... the compiler only validates BarBuilder when compiling libbar, but it fails because
369      * of libfoo's SharedBuilder (which could have been compiled in a previous pass).
370      * So we can't point to SharedBuilder#badSetter as the subject of the BarBuilder validation
371      * failure.
372      *
373      * This check is a little more strict than necessary -- ideally we'd check if method's enclosing
374      * class was included in this compile run.  But that's hard, and this is close enough.
375      */
error( ExecutableElement method, String enclosedError, String inheritedError, Object... extraArgs)376     private void error(
377         ExecutableElement method,
378         String enclosedError,
379         String inheritedError,
380         Object... extraArgs) {
381       if (method.getEnclosingElement().equals(type)) {
382         report.addError(String.format(enclosedError, extraArgs), method);
383       } else {
384         report.addError(String.format(inheritedError, ObjectArrays.concat(extraArgs, method)));
385       }
386     }
387 
388     /** Validates that the given {@code method} is not generic. * */
validateNotGeneric(ExecutableElement method)389     private void validateNotGeneric(ExecutableElement method) {
390       if (!method.getTypeParameters().isEmpty()) {
391         error(
392             method,
393             messages.methodsMayNotHaveTypeParameters(),
394             messages.inheritedMethodsMayNotHaveTypeParameters());
395       }
396     }
397 
398     /**
399      * Returns all methods defind in {@code componentType} which are not inherited from a supertype.
400      */
methodsOnlyInComponent(TypeElement componentType)401     private ImmutableSet<ExecutableElement> methodsOnlyInComponent(TypeElement componentType) {
402       // TODO(ronshapiro): Ideally this shouldn't return methods which are redeclared from a
403       // supertype, but do not change the return type. We don't have a good/simple way of checking
404       // that, and it doesn't seem likely, so the warning won't be too bad.
405       return ImmutableSet.copyOf(methodsIn(componentType.getEnclosedElements()));
406     }
407   }
408 }
409