• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.asType;
20 import static com.google.auto.common.MoreElements.isAnnotationPresent;
21 import static com.google.auto.common.MoreTypes.asDeclared;
22 import static com.google.auto.common.MoreTypes.asExecutable;
23 import static com.google.auto.common.MoreTypes.asTypeElement;
24 import static com.google.common.base.Verify.verify;
25 import static com.google.common.collect.Iterables.getOnlyElement;
26 import static com.google.common.collect.Multimaps.asMap;
27 import static com.google.common.collect.Sets.intersection;
28 import static dagger.internal.codegen.base.ComponentAnnotation.anyComponentAnnotation;
29 import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation;
30 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
31 import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.creatorAnnotationsFor;
32 import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.productionCreatorAnnotations;
33 import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.subcomponentCreatorAnnotations;
34 import static dagger.internal.codegen.binding.ComponentKind.annotationsFor;
35 import static dagger.internal.codegen.binding.ConfigurationAnnotations.enclosedAnnotatedTypes;
36 import static dagger.internal.codegen.binding.ConfigurationAnnotations.getTransitiveModules;
37 import static dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages.builderMethodRequiresNoArgs;
38 import static dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages.moreThanOneRefToSubcomponent;
39 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
40 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
41 import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror;
42 import static dagger.internal.codegen.langmodel.DaggerElements.getAnyAnnotation;
43 import static java.util.Comparator.comparing;
44 import static javax.lang.model.element.ElementKind.CLASS;
45 import static javax.lang.model.element.ElementKind.INTERFACE;
46 import static javax.lang.model.element.Modifier.ABSTRACT;
47 import static javax.lang.model.type.TypeKind.VOID;
48 import static javax.lang.model.util.ElementFilter.methodsIn;
49 
50 import com.google.auto.common.MoreTypes;
51 import com.google.common.collect.HashMultimap;
52 import com.google.common.collect.ImmutableList;
53 import com.google.common.collect.ImmutableSet;
54 import com.google.common.collect.LinkedHashMultimap;
55 import com.google.common.collect.Maps;
56 import com.google.common.collect.SetMultimap;
57 import com.google.common.collect.Sets;
58 import dagger.Component;
59 import dagger.Reusable;
60 import dagger.internal.codegen.base.ClearableCache;
61 import dagger.internal.codegen.base.ComponentAnnotation;
62 import dagger.internal.codegen.binding.ComponentKind;
63 import dagger.internal.codegen.binding.DependencyRequestFactory;
64 import dagger.internal.codegen.binding.ErrorMessages;
65 import dagger.internal.codegen.binding.MethodSignatureFormatter;
66 import dagger.internal.codegen.binding.ModuleKind;
67 import dagger.internal.codegen.langmodel.DaggerElements;
68 import dagger.internal.codegen.langmodel.DaggerTypes;
69 import dagger.model.DependencyRequest;
70 import dagger.model.Key;
71 import dagger.producers.CancellationPolicy;
72 import dagger.producers.ProductionComponent;
73 import java.lang.annotation.Annotation;
74 import java.util.Collection;
75 import java.util.HashMap;
76 import java.util.HashSet;
77 import java.util.List;
78 import java.util.Map;
79 import java.util.Optional;
80 import java.util.Set;
81 import javax.inject.Inject;
82 import javax.inject.Singleton;
83 import javax.lang.model.element.AnnotationMirror;
84 import javax.lang.model.element.Element;
85 import javax.lang.model.element.ExecutableElement;
86 import javax.lang.model.element.TypeElement;
87 import javax.lang.model.element.VariableElement;
88 import javax.lang.model.type.DeclaredType;
89 import javax.lang.model.type.ExecutableType;
90 import javax.lang.model.type.TypeMirror;
91 import javax.lang.model.type.TypeVisitor;
92 import javax.lang.model.util.SimpleTypeVisitor8;
93 
94 /**
95  * Performs superficial validation of the contract of the {@link Component} and {@link
96  * ProductionComponent} annotations.
97  */
98 @Singleton
99 public final class ComponentValidator implements ClearableCache {
100   private final DaggerElements elements;
101   private final DaggerTypes types;
102   private final ModuleValidator moduleValidator;
103   private final ComponentCreatorValidator creatorValidator;
104   private final DependencyRequestValidator dependencyRequestValidator;
105   private final MembersInjectionValidator membersInjectionValidator;
106   private final MethodSignatureFormatter methodSignatureFormatter;
107   private final DependencyRequestFactory dependencyRequestFactory;
108   private final Map<TypeElement, ValidationReport<TypeElement>> reports = new HashMap<>();
109 
110   @Inject
ComponentValidator( DaggerElements elements, DaggerTypes types, ModuleValidator moduleValidator, ComponentCreatorValidator creatorValidator, DependencyRequestValidator dependencyRequestValidator, MembersInjectionValidator membersInjectionValidator, MethodSignatureFormatter methodSignatureFormatter, DependencyRequestFactory dependencyRequestFactory)111   ComponentValidator(
112       DaggerElements elements,
113       DaggerTypes types,
114       ModuleValidator moduleValidator,
115       ComponentCreatorValidator creatorValidator,
116       DependencyRequestValidator dependencyRequestValidator,
117       MembersInjectionValidator membersInjectionValidator,
118       MethodSignatureFormatter methodSignatureFormatter,
119       DependencyRequestFactory dependencyRequestFactory) {
120     this.elements = elements;
121     this.types = types;
122     this.moduleValidator = moduleValidator;
123     this.creatorValidator = creatorValidator;
124     this.dependencyRequestValidator = dependencyRequestValidator;
125     this.membersInjectionValidator = membersInjectionValidator;
126     this.methodSignatureFormatter = methodSignatureFormatter;
127     this.dependencyRequestFactory = dependencyRequestFactory;
128   }
129 
130   @Override
clearCache()131   public void clearCache() {
132     reports.clear();
133   }
134 
135   /** Validates the given component. */
validate(TypeElement component)136   public ValidationReport<TypeElement> validate(TypeElement component) {
137     return reentrantComputeIfAbsent(reports, component, this::validateUncached);
138   }
139 
validateUncached(TypeElement component)140   private ValidationReport<TypeElement> validateUncached(TypeElement component) {
141     return new ElementValidator(component).validateElement();
142   }
143 
144   private class ElementValidator {
145     private final TypeElement component;
146     private final ValidationReport.Builder<TypeElement> report;
147     private final ImmutableSet<ComponentKind> componentKinds;
148 
149     // Populated by ComponentMethodValidators
150     private final SetMultimap<Element, ExecutableElement> referencedSubcomponents =
151         LinkedHashMultimap.create();
152 
ElementValidator(TypeElement component)153     ElementValidator(TypeElement component) {
154       this.component = component;
155       this.report = ValidationReport.about(component);
156       this.componentKinds = ComponentKind.getComponentKinds(component);
157     }
158 
componentKind()159     private ComponentKind componentKind() {
160       return getOnlyElement(componentKinds);
161     }
162 
componentAnnotation()163     private ComponentAnnotation componentAnnotation() {
164       return anyComponentAnnotation(component).get();
165     }
166 
componentType()167     private DeclaredType componentType() {
168       return asDeclared(component.asType());
169     }
170 
validateElement()171     ValidationReport<TypeElement> validateElement() {
172       if (componentKinds.size() > 1) {
173         return moreThanOneComponentAnnotation();
174       }
175 
176       validateUseOfCancellationPolicy();
177       validateIsAbstractType();
178       validateCreators();
179       validateNoReusableAnnotation();
180       validateComponentMethods();
181       validateNoConflictingEntryPoints();
182       validateSubcomponentReferences();
183       validateComponentDependencies();
184       validateReferencedModules();
185       validateSubcomponents();
186 
187       return report.build();
188     }
189 
moreThanOneComponentAnnotation()190     private ValidationReport<TypeElement> moreThanOneComponentAnnotation() {
191       String error =
192           "Components may not be annotated with more than one component annotation: found "
193               + annotationsFor(componentKinds);
194       report.addError(error, component);
195       return report.build();
196     }
197 
validateUseOfCancellationPolicy()198     private void validateUseOfCancellationPolicy() {
199       if (isAnnotationPresent(component, CancellationPolicy.class)
200           && !componentKind().isProducer()) {
201         report.addError(
202             "@CancellationPolicy may only be applied to production components and subcomponents",
203             component);
204       }
205     }
206 
validateIsAbstractType()207     private void validateIsAbstractType() {
208       if (!component.getKind().equals(INTERFACE)
209           && !(component.getKind().equals(CLASS) && component.getModifiers().contains(ABSTRACT))) {
210         report.addError(
211             String.format(
212                 "@%s may only be applied to an interface or abstract class",
213                 componentKind().annotation().getSimpleName()),
214             component);
215       }
216     }
217 
validateCreators()218     private void validateCreators() {
219       ImmutableList<DeclaredType> creators =
220           creatorAnnotationsFor(componentAnnotation()).stream()
221               .flatMap(annotation -> enclosedAnnotatedTypes(component, annotation).stream())
222               .collect(toImmutableList());
223       creators.forEach(
224           creator -> report.addSubreport(creatorValidator.validate(asTypeElement(creator))));
225       if (creators.size() > 1) {
226         report.addError(
227             String.format(
228                 ErrorMessages.componentMessagesFor(componentKind()).moreThanOne(), creators),
229             component);
230       }
231     }
232 
validateNoReusableAnnotation()233     private void validateNoReusableAnnotation() {
234       Optional<AnnotationMirror> reusableAnnotation =
235           getAnnotationMirror(component, Reusable.class);
236       if (reusableAnnotation.isPresent()) {
237         report.addError(
238             "@Reusable cannot be applied to components or subcomponents",
239             component,
240             reusableAnnotation.get());
241       }
242     }
243 
validateComponentMethods()244     private void validateComponentMethods() {
245       elements.getUnimplementedMethods(component).stream()
246           .map(ComponentMethodValidator::new)
247           .forEachOrdered(ComponentMethodValidator::validateMethod);
248     }
249 
250     private class ComponentMethodValidator {
251       private final ExecutableElement method;
252       private final ExecutableType resolvedMethod;
253       private final List<? extends TypeMirror> parameterTypes;
254       private final List<? extends VariableElement> parameters;
255       private final TypeMirror returnType;
256 
ComponentMethodValidator(ExecutableElement method)257       ComponentMethodValidator(ExecutableElement method) {
258         this.method = method;
259         this.resolvedMethod = asExecutable(types.asMemberOf(componentType(), method));
260         this.parameterTypes = resolvedMethod.getParameterTypes();
261         this.parameters = method.getParameters();
262         this.returnType = resolvedMethod.getReturnType();
263       }
264 
validateMethod()265       void validateMethod() {
266         validateNoTypeVariables();
267 
268         // abstract methods are ones we have to implement, so they each need to be validated
269         // first, check the return type. if it's a subcomponent, validate that method as
270         // such.
271         Optional<AnnotationMirror> subcomponentAnnotation = subcomponentAnnotation();
272         if (subcomponentAnnotation.isPresent()) {
273           validateSubcomponentFactoryMethod(subcomponentAnnotation.get());
274         } else if (subcomponentCreatorAnnotation().isPresent()) {
275           validateSubcomponentCreatorMethod();
276         } else {
277           // if it's not a subcomponent...
278           switch (parameters.size()) {
279             case 0:
280               validateProvisionMethod();
281               break;
282             case 1:
283               validateMembersInjectionMethod();
284               break;
285             default:
286               reportInvalidMethod();
287               break;
288           }
289         }
290       }
291 
validateNoTypeVariables()292       private void validateNoTypeVariables() {
293         if (!resolvedMethod.getTypeVariables().isEmpty()) {
294           report.addError("Component methods cannot have type variables", method);
295         }
296       }
297 
subcomponentAnnotation()298       private Optional<AnnotationMirror> subcomponentAnnotation() {
299         return checkForAnnotations(
300             returnType,
301             componentKind().legalSubcomponentKinds().stream()
302                 .map(ComponentKind::annotation)
303                 .collect(toImmutableSet()));
304       }
305 
subcomponentCreatorAnnotation()306       private Optional<AnnotationMirror> subcomponentCreatorAnnotation() {
307         return checkForAnnotations(
308             returnType,
309             componentAnnotation().isProduction()
310                 ? intersection(subcomponentCreatorAnnotations(), productionCreatorAnnotations())
311                 : subcomponentCreatorAnnotations());
312       }
313 
validateSubcomponentFactoryMethod(AnnotationMirror subcomponentAnnotation)314       private void validateSubcomponentFactoryMethod(AnnotationMirror subcomponentAnnotation) {
315         referencedSubcomponents.put(MoreTypes.asElement(returnType), method);
316 
317         ComponentKind subcomponentKind =
318             ComponentKind.forAnnotatedElement(MoreTypes.asTypeElement(returnType)).get();
319         ImmutableSet<TypeElement> moduleTypes =
320             ComponentAnnotation.componentAnnotation(subcomponentAnnotation).modules();
321 
322         // TODO(gak): This logic maybe/probably shouldn't live here as it requires us to traverse
323         // subcomponents and their modules separately from how it is done in ComponentDescriptor and
324         // ModuleDescriptor
325         @SuppressWarnings("deprecation")
326         ImmutableSet<TypeElement> transitiveModules =
327             getTransitiveModules(types, elements, moduleTypes);
328 
329         Set<TypeElement> variableTypes = Sets.newHashSet();
330 
331         for (int i = 0; i < parameterTypes.size(); i++) {
332           VariableElement parameter = parameters.get(i);
333           TypeMirror parameterType = parameterTypes.get(i);
334           Optional<TypeElement> moduleType =
335               parameterType.accept(
336                   new SimpleTypeVisitor8<Optional<TypeElement>, Void>() {
337                     @Override
338                     protected Optional<TypeElement> defaultAction(TypeMirror e, Void p) {
339                       return Optional.empty();
340                     }
341 
342                     @Override
343                     public Optional<TypeElement> visitDeclared(DeclaredType t, Void p) {
344                       for (ModuleKind moduleKind : subcomponentKind.legalModuleKinds()) {
345                         if (isAnnotationPresent(t.asElement(), moduleKind.annotation())) {
346                           return Optional.of(MoreTypes.asTypeElement(t));
347                         }
348                       }
349                       return Optional.empty();
350                     }
351                   },
352                   null);
353           if (moduleType.isPresent()) {
354             if (variableTypes.contains(moduleType.get())) {
355               report.addError(
356                   String.format(
357                       "A module may only occur once an an argument in a Subcomponent factory "
358                           + "method, but %s was already passed.",
359                       moduleType.get().getQualifiedName()),
360                   parameter);
361             }
362             if (!transitiveModules.contains(moduleType.get())) {
363               report.addError(
364                   String.format(
365                       "%s is present as an argument to the %s factory method, but is not one of the"
366                           + " modules used to implement the subcomponent.",
367                       moduleType.get().getQualifiedName(),
368                       MoreTypes.asTypeElement(returnType).getQualifiedName()),
369                   method);
370             }
371             variableTypes.add(moduleType.get());
372           } else {
373             report.addError(
374                 String.format(
375                     "Subcomponent factory methods may only accept modules, but %s is not.",
376                     parameterType),
377                 parameter);
378           }
379         }
380       }
381 
validateSubcomponentCreatorMethod()382       private void validateSubcomponentCreatorMethod() {
383         referencedSubcomponents.put(MoreTypes.asElement(returnType).getEnclosingElement(), method);
384 
385         if (!parameters.isEmpty()) {
386           report.addError(builderMethodRequiresNoArgs(), method);
387         }
388 
389         TypeElement creatorElement = MoreTypes.asTypeElement(returnType);
390         // TODO(sameb): The creator validator right now assumes the element is being compiled
391         // in this pass, which isn't true here.  We should change error messages to spit out
392         // this method as the subject and add the original subject to the message output.
393         report.addSubreport(creatorValidator.validate(creatorElement));
394       }
395 
validateProvisionMethod()396       private void validateProvisionMethod() {
397         dependencyRequestValidator.validateDependencyRequest(report, method, returnType);
398       }
399 
validateMembersInjectionMethod()400       private void validateMembersInjectionMethod() {
401         TypeMirror parameterType = getOnlyElement(parameterTypes);
402         report.addSubreport(
403             membersInjectionValidator.validateMembersInjectionMethod(method, parameterType));
404         if (!(returnType.getKind().equals(VOID) || types.isSameType(returnType, parameterType))) {
405           report.addError(
406               "Members injection methods may only return the injected type or void.", method);
407         }
408       }
409 
reportInvalidMethod()410       private void reportInvalidMethod() {
411         report.addError(
412             "This method isn't a valid provision method, members injection method or "
413                 + "subcomponent factory method. Dagger cannot implement this method",
414             method);
415       }
416     }
417 
validateNoConflictingEntryPoints()418     private void validateNoConflictingEntryPoints() {
419       // Collect entry point methods that are not overridden by others. If the "same" method is
420       // inherited from more than one supertype, each will be in the multimap.
421       SetMultimap<String, ExecutableElement> entryPointMethods = HashMultimap.create();
422 
423       methodsIn(elements.getAllMembers(component)).stream()
424           .filter(
425               method ->
426                   isEntryPoint(method, asExecutable(types.asMemberOf(componentType(), method))))
427           .forEach(
428               method ->
429                   addMethodUnlessOverridden(
430                       method, entryPointMethods.get(method.getSimpleName().toString())));
431 
432       for (Set<ExecutableElement> methods : asMap(entryPointMethods).values()) {
433         if (distinctKeys(methods).size() > 1) {
434           reportConflictingEntryPoints(methods);
435         }
436       }
437     }
438 
reportConflictingEntryPoints(Collection<ExecutableElement> methods)439     private void reportConflictingEntryPoints(Collection<ExecutableElement> methods) {
440       verify(
441           methods.stream().map(ExecutableElement::getEnclosingElement).distinct().count()
442               == methods.size(),
443           "expected each method to be declared on a different type: %s",
444           methods);
445       StringBuilder message = new StringBuilder("conflicting entry point declarations:");
446       methodSignatureFormatter
447           .typedFormatter(componentType())
448           .formatIndentedList(
449               message,
450               ImmutableList.sortedCopyOf(
451                   comparing(
452                       method -> asType(method.getEnclosingElement()).getQualifiedName().toString()),
453                   methods),
454               1);
455       report.addError(message.toString());
456     }
457 
validateSubcomponentReferences()458     private void validateSubcomponentReferences() {
459       Maps.filterValues(referencedSubcomponents.asMap(), methods -> methods.size() > 1)
460           .forEach(
461               (subcomponent, methods) ->
462                   report.addError(
463                       String.format(moreThanOneRefToSubcomponent(), subcomponent, methods),
464                       component));
465     }
466 
validateComponentDependencies()467     private void validateComponentDependencies() {
468       for (TypeMirror type : componentAnnotation().dependencyTypes()) {
469         type.accept(CHECK_DEPENDENCY_TYPES, report);
470       }
471     }
472 
validateReferencedModules()473     private void validateReferencedModules() {
474       report.addSubreport(
475           moduleValidator.validateReferencedModules(
476               component,
477               componentAnnotation().annotation(),
478               componentKind().legalModuleKinds(),
479               new HashSet<>()));
480     }
481 
validateSubcomponents()482     private void validateSubcomponents() {
483       // Make sure we validate any subcomponents we're referencing.
484       for (Element subcomponent : referencedSubcomponents.keySet()) {
485         ValidationReport<TypeElement> subreport = validate(asType(subcomponent));
486         report.addSubreport(subreport);
487       }
488     }
489 
distinctKeys(Set<ExecutableElement> methods)490     private ImmutableSet<Key> distinctKeys(Set<ExecutableElement> methods) {
491       return methods.stream()
492           .map(this::dependencyRequest)
493           .map(DependencyRequest::key)
494           .collect(toImmutableSet());
495     }
496 
dependencyRequest(ExecutableElement method)497     private DependencyRequest dependencyRequest(ExecutableElement method) {
498       ExecutableType methodType = asExecutable(types.asMemberOf(componentType(), method));
499       return ComponentKind.forAnnotatedElement(component).get().isProducer()
500           ? dependencyRequestFactory.forComponentProductionMethod(method, methodType)
501           : dependencyRequestFactory.forComponentProvisionMethod(method, methodType);
502     }
503   }
504 
isEntryPoint(ExecutableElement method, ExecutableType methodType)505   private static boolean isEntryPoint(ExecutableElement method, ExecutableType methodType) {
506     return method.getModifiers().contains(ABSTRACT)
507         && method.getParameters().isEmpty()
508         && !methodType.getReturnType().getKind().equals(VOID)
509         && methodType.getTypeVariables().isEmpty();
510   }
511 
addMethodUnlessOverridden(ExecutableElement method, Set<ExecutableElement> methods)512   private void addMethodUnlessOverridden(ExecutableElement method, Set<ExecutableElement> methods) {
513     if (methods.stream().noneMatch(existingMethod -> overridesAsDeclared(existingMethod, method))) {
514       methods.removeIf(existingMethod -> overridesAsDeclared(method, existingMethod));
515       methods.add(method);
516     }
517   }
518 
519   /**
520    * Returns {@code true} if {@code overrider} overrides {@code overridden} considered from within
521    * the type that declares {@code overrider}.
522    */
523   // TODO(dpb): Does this break for ECJ?
overridesAsDeclared(ExecutableElement overrider, ExecutableElement overridden)524   private boolean overridesAsDeclared(ExecutableElement overrider, ExecutableElement overridden) {
525     return elements.overrides(overrider, overridden, asType(overrider.getEnclosingElement()));
526   }
527 
528   private static final TypeVisitor<Void, ValidationReport.Builder<?>> CHECK_DEPENDENCY_TYPES =
529       new SimpleTypeVisitor8<Void, ValidationReport.Builder<?>>() {
530         @Override
531         protected Void defaultAction(TypeMirror type, ValidationReport.Builder<?> report) {
532           report.addError(type + " is not a valid component dependency type");
533           return null;
534         }
535 
536         @Override
537         public Void visitDeclared(DeclaredType type, ValidationReport.Builder<?> report) {
538           if (moduleAnnotation(MoreTypes.asTypeElement(type)).isPresent()) {
539             report.addError(type + " is a module, which cannot be a component dependency");
540           }
541           return null;
542         }
543       };
544 
checkForAnnotations( TypeMirror type, final Set<? extends Class<? extends Annotation>> annotations)545   private static Optional<AnnotationMirror> checkForAnnotations(
546       TypeMirror type, final Set<? extends Class<? extends Annotation>> annotations) {
547     return type.accept(
548         new SimpleTypeVisitor8<Optional<AnnotationMirror>, Void>(Optional.empty()) {
549           @Override
550           public Optional<AnnotationMirror> visitDeclared(DeclaredType t, Void p) {
551             return getAnyAnnotation(t.asElement(), annotations);
552           }
553         },
554         null);
555   }
556 }
557