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