1/*
2 * Copyright (C) 2014 Google, Inc.
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 */
16package dagger.internal.codegen;
17
18import com.google.auto.common.MoreElements;
19import com.google.auto.common.MoreTypes;
20import com.google.auto.value.AutoValue;
21import com.google.common.base.Optional;
22import com.google.common.collect.ImmutableList;
23import com.google.common.collect.ImmutableSet;
24import com.google.common.collect.Iterables;
25import com.google.common.collect.LinkedHashMultimap;
26import com.google.common.collect.Multimap;
27import com.google.common.collect.Sets;
28import dagger.Component;
29import dagger.Module;
30import dagger.Subcomponent;
31import java.lang.annotation.Annotation;
32import java.util.Collection;
33import java.util.List;
34import java.util.Map;
35import java.util.Set;
36import javax.lang.model.element.AnnotationMirror;
37import javax.lang.model.element.Element;
38import javax.lang.model.element.ExecutableElement;
39import javax.lang.model.element.TypeElement;
40import javax.lang.model.element.VariableElement;
41import javax.lang.model.type.DeclaredType;
42import javax.lang.model.type.ExecutableType;
43import javax.lang.model.type.TypeMirror;
44import javax.lang.model.util.ElementFilter;
45import javax.lang.model.util.Elements;
46import javax.lang.model.util.SimpleTypeVisitor6;
47import javax.lang.model.util.Types;
48
49import static com.google.auto.common.MoreElements.getAnnotationMirror;
50import static dagger.internal.codegen.ConfigurationAnnotations.enclosedBuilders;
51import static dagger.internal.codegen.ConfigurationAnnotations.getComponentModules;
52import static dagger.internal.codegen.ConfigurationAnnotations.getTransitiveModules;
53import static javax.lang.model.element.ElementKind.CLASS;
54import static javax.lang.model.element.ElementKind.INTERFACE;
55import static javax.lang.model.element.Modifier.ABSTRACT;
56import static javax.lang.model.type.TypeKind.VOID;
57
58/**
59 * Performs superficial validation of the contract of the {@link Component} annotation.
60 *
61 * @author Gregory Kick
62 */
63final class ComponentValidator {
64  private final Elements elements;
65  private final Types types;
66  private final ComponentDescriptor.Kind componentType;
67  private final ModuleValidator moduleValidator;
68  private final ComponentValidator subcomponentValidator;
69  private final BuilderValidator subcomponentBuilderValidator;
70
71  private ComponentValidator(Elements elements,
72      Types types,
73      ModuleValidator moduleValidator,
74      BuilderValidator subcomponentBuilderValidator) {
75    this.elements = elements;
76    this.types = types;
77    this.componentType = ComponentDescriptor.Kind.SUBCOMPONENT;
78    this.moduleValidator = moduleValidator;
79    this.subcomponentValidator = this;
80    this.subcomponentBuilderValidator = subcomponentBuilderValidator;
81  }
82
83  private ComponentValidator(Elements elements,
84      Types types,
85      ModuleValidator moduleValidator,
86      ComponentValidator subcomponentValidator,
87      BuilderValidator subcomponentBuilderValidator) {
88    this.elements = elements;
89    this.types = types;
90    this.componentType = ComponentDescriptor.Kind.COMPONENT;
91    this.moduleValidator = moduleValidator;
92    this.subcomponentValidator = subcomponentValidator;
93    this.subcomponentBuilderValidator = subcomponentBuilderValidator;
94  }
95
96  static ComponentValidator createForComponent(Elements elements,
97      Types types,
98      ModuleValidator moduleValidator,
99      ComponentValidator subcomponentValidator,
100      BuilderValidator subcomponentBuilderValidator) {
101    return new ComponentValidator(elements,
102        types,
103        moduleValidator,
104        subcomponentValidator,
105        subcomponentBuilderValidator);
106  }
107
108  static ComponentValidator createForSubcomponent(Elements elements,
109      Types types,
110      ModuleValidator moduleValidator,
111      BuilderValidator subcomponentBuilderValidator) {
112    return new ComponentValidator(elements,
113        types,
114        moduleValidator,
115        subcomponentBuilderValidator);
116  }
117
118  @AutoValue
119  static abstract class ComponentValidationReport {
120    abstract Set<Element> referencedSubcomponents();
121    abstract ValidationReport<TypeElement> report();
122  }
123
124  /**
125   * Validates the given component subject. Also validates any referenced subcomponents that aren't
126   * already included in the {@code validatedSubcomponents} set.
127   */
128  public ComponentValidationReport validate(final TypeElement subject,
129      Set<? extends Element> validatedSubcomponents,
130      Set<? extends Element> validatedSubcomponentBuilders) {
131    ValidationReport.Builder<TypeElement> builder = ValidationReport.about(subject);
132
133    if (!subject.getKind().equals(INTERFACE)
134        && !(subject.getKind().equals(CLASS) && subject.getModifiers().contains(ABSTRACT))) {
135      builder.addError(
136          String.format(
137              "@%s may only be applied to an interface or abstract class",
138              componentType.annotationType().getSimpleName()),
139          subject);
140    }
141
142    ImmutableList<DeclaredType> builders =
143        enclosedBuilders(subject, componentType.builderAnnotationType());
144    if (builders.size() > 1) {
145      builder.addError(
146          String.format(ErrorMessages.builderMsgsFor(componentType).moreThanOne(), builders),
147          subject);
148    }
149
150    DeclaredType subjectType = MoreTypes.asDeclared(subject.asType());
151
152    // TODO(gak): This should use Util.findLocalAndInheritedMethods, otherwise
153    // it can return a logical method multiple times (including overrides, etc.)
154    List<? extends Element> members = elements.getAllMembers(subject);
155    Multimap<Element, ExecutableElement> referencedSubcomponents = LinkedHashMultimap.create();
156    for (ExecutableElement method : ElementFilter.methodsIn(members)) {
157      if (method.getModifiers().contains(ABSTRACT)) {
158        ExecutableType resolvedMethod =
159            MoreTypes.asExecutable(types.asMemberOf(subjectType, method));
160        List<? extends TypeMirror> parameterTypes = resolvedMethod.getParameterTypes();
161        List<? extends VariableElement> parameters = method.getParameters();
162        TypeMirror returnType = resolvedMethod.getReturnType();
163
164        // abstract methods are ones we have to implement, so they each need to be validated
165        // first, check the return type.  if it's a subcomponent, validate that method as such.
166        Optional<AnnotationMirror> subcomponentAnnotation =
167            checkForAnnotation(returnType, Subcomponent.class);
168        Optional<AnnotationMirror> subcomponentBuilderAnnotation =
169            checkForAnnotation(returnType, Subcomponent.Builder.class);
170        if (subcomponentAnnotation.isPresent()) {
171          referencedSubcomponents.put(MoreTypes.asElement(returnType), method);
172          validateSubcomponentMethod(builder,
173              method,
174              parameters,
175              parameterTypes,
176              returnType,
177              subcomponentAnnotation);
178        } else if (subcomponentBuilderAnnotation.isPresent()) {
179          referencedSubcomponents.put(MoreTypes.asElement(returnType).getEnclosingElement(),
180              method);
181          validateSubcomponentBuilderMethod(builder,
182              method,
183              parameters,
184              returnType,
185              validatedSubcomponentBuilders);
186        } else {
187          // if it's not a subcomponent...
188          switch (parameters.size()) {
189            case 0:
190              // no parameters means that it is a provision method
191              // basically, there are no restrictions here.  \o/
192              break;
193            case 1:
194              // one parameter means that it's a members injection method
195              TypeMirror onlyParameter = Iterables.getOnlyElement(parameterTypes);
196              if (!(returnType.getKind().equals(VOID)
197                  || types.isSameType(returnType, onlyParameter))) {
198                builder.addError(
199                    "Members injection methods may only return the injected type or void.", method);
200              }
201              break;
202            default:
203              // this isn't any method that we know how to implement...
204              builder.addError(
205                  "This method isn't a valid provision method, members injection method or "
206                      + "subcomponent factory method. Dagger cannot implement this method",
207                  method);
208              break;
209          }
210        }
211      }
212    }
213
214    for (Map.Entry<Element, Collection<ExecutableElement>> entry :
215        referencedSubcomponents.asMap().entrySet()) {
216      if (entry.getValue().size() > 1) {
217        builder.addError(
218            String.format(
219                ErrorMessages.SubcomponentBuilderMessages.INSTANCE.moreThanOneRefToSubcomponent(),
220                entry.getKey(),
221                entry.getValue()),
222            subject);
223      }
224    }
225
226    AnnotationMirror componentMirror =
227        getAnnotationMirror(subject, componentType.annotationType()).get();
228    ImmutableList<TypeMirror> moduleTypes = getComponentModules(componentMirror);
229    moduleValidator.validateReferencedModules(subject, builder, moduleTypes);
230
231    // Make sure we validate any subcomponents we're referencing, unless we know we validated
232    // them already in this pass.
233    // TODO(sameb): If subcomponents refer to each other and both aren't in
234    //              'validatedSubcomponents' (e.g, both aren't compiled in this pass),
235    //              then this can loop forever.
236    ImmutableSet.Builder<Element> allSubcomponents =
237        ImmutableSet.<Element>builder().addAll(referencedSubcomponents.keySet());
238    for (Element subcomponent :
239        Sets.difference(referencedSubcomponents.keySet(), validatedSubcomponents)) {
240      ComponentValidationReport subreport = subcomponentValidator.validate(
241          MoreElements.asType(subcomponent), validatedSubcomponents, validatedSubcomponentBuilders);
242      builder.addItems(subreport.report().items());
243      allSubcomponents.addAll(subreport.referencedSubcomponents());
244    }
245
246    return new AutoValue_ComponentValidator_ComponentValidationReport(allSubcomponents.build(),
247        builder.build());
248  }
249
250  private void validateSubcomponentMethod(final ValidationReport.Builder<TypeElement> builder,
251      ExecutableElement method,
252      List<? extends VariableElement> parameters,
253      List<? extends TypeMirror> parameterTypes,
254      TypeMirror returnType,
255      Optional<AnnotationMirror> subcomponentAnnotation) {
256    ImmutableSet<TypeElement> moduleTypes =
257        MoreTypes.asTypeElements(getComponentModules(subcomponentAnnotation.get()));
258
259    // TODO(gak): This logic maybe/probably shouldn't live here as it requires us to traverse
260    // subcomponents and their modules separately from how it is done in ComponentDescriptor and
261    // ModuleDescriptor
262    @SuppressWarnings("deprecation")
263    ImmutableSet<TypeElement> transitiveModules =
264        getTransitiveModules(types, elements, moduleTypes);
265
266    Set<TypeElement> variableTypes = Sets.newHashSet();
267
268    for (int i = 0; i < parameterTypes.size(); i++) {
269      VariableElement parameter = parameters.get(i);
270      TypeMirror parameterType = parameterTypes.get(i);
271      Optional<TypeElement> moduleType = parameterType.accept(
272          new SimpleTypeVisitor6<Optional<TypeElement>, Void>() {
273            @Override protected Optional<TypeElement> defaultAction(TypeMirror e, Void p) {
274              return Optional.absent();
275            }
276
277            @Override public Optional<TypeElement> visitDeclared(DeclaredType t, Void p) {
278              return MoreElements.isAnnotationPresent(t.asElement(), Module.class)
279                  ? Optional.of(MoreTypes.asTypeElement(t))
280                  : Optional.<TypeElement>absent();
281            }
282          }, null);
283      if (moduleType.isPresent()) {
284        if (variableTypes.contains(moduleType.get())) {
285          builder.addError(
286              String.format(
287                  "A module may only occur once an an argument in a Subcomponent factory "
288                      + "method, but %s was already passed.",
289                  moduleType.get().getQualifiedName()),
290              parameter);
291        }
292        if (!transitiveModules.contains(moduleType.get())) {
293          builder.addError(
294              String.format(
295                  "%s is present as an argument to the %s factory method, but is not one of the"
296                      + " modules used to implement the subcomponent.",
297                  moduleType.get().getQualifiedName(),
298                  MoreTypes.asTypeElement(returnType).getQualifiedName()),
299              method);
300        }
301        variableTypes.add(moduleType.get());
302      } else {
303        builder.addError(
304            String.format(
305                "Subcomponent factory methods may only accept modules, but %s is not.",
306                parameterType),
307            parameter);
308      }
309    }
310  }
311
312  private void validateSubcomponentBuilderMethod(ValidationReport.Builder<TypeElement> builder,
313      ExecutableElement method, List<? extends VariableElement> parameters, TypeMirror returnType,
314      Set<? extends Element> validatedSubcomponentBuilders) {
315
316    if (!parameters.isEmpty()) {
317      builder.addError(
318          ErrorMessages.SubcomponentBuilderMessages.INSTANCE.builderMethodRequiresNoArgs(), method);
319    }
320
321    // If we haven't already validated the subcomponent builder itself, validate it now.
322    TypeElement builderElement = MoreTypes.asTypeElement(returnType);
323    if (!validatedSubcomponentBuilders.contains(builderElement)) {
324      // TODO(sameb): The builder validator right now assumes the element is being compiled
325      // in this pass, which isn't true here.  We should change error messages to spit out
326      // this method as the subject and add the original subject to the message output.
327      builder.addItems(subcomponentBuilderValidator.validate(builderElement).items());
328    }
329  }
330
331  private Optional<AnnotationMirror> checkForAnnotation(TypeMirror type,
332      final Class<? extends Annotation> annotation) {
333    return type.accept(new SimpleTypeVisitor6<Optional<AnnotationMirror>, Void>() {
334      @Override
335      protected Optional<AnnotationMirror> defaultAction(TypeMirror e, Void p) {
336        return Optional.absent();
337      }
338
339      @Override
340      public Optional<AnnotationMirror> visitDeclared(DeclaredType t, Void p) {
341        return MoreElements.getAnnotationMirror(t.asElement(), annotation);
342      }
343    }, null);
344  }
345}
346