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.Visibility;
20import com.google.common.base.Function;
21import com.google.common.base.Joiner;
22import com.google.common.base.Predicate;
23import com.google.common.collect.ArrayListMultimap;
24import com.google.common.collect.FluentIterable;
25import com.google.common.collect.ImmutableList;
26import com.google.common.collect.ImmutableSet;
27import com.google.common.collect.ListMultimap;
28import com.google.common.collect.Sets;
29import dagger.Module;
30import dagger.producers.ProducerModule;
31import java.lang.annotation.Annotation;
32import java.util.Collection;
33import java.util.List;
34import java.util.Map.Entry;
35import java.util.Set;
36import javax.lang.model.element.AnnotationMirror;
37import javax.lang.model.element.Element;
38import javax.lang.model.element.ElementKind;
39import javax.lang.model.element.ExecutableElement;
40import javax.lang.model.element.TypeElement;
41import javax.lang.model.type.DeclaredType;
42import javax.lang.model.type.TypeMirror;
43import javax.lang.model.util.ElementFilter;
44import javax.lang.model.util.Elements;
45import javax.lang.model.util.SimpleTypeVisitor6;
46import javax.lang.model.util.Types;
47
48import static com.google.auto.common.MoreElements.getAnnotationMirror;
49import static com.google.auto.common.MoreElements.isAnnotationPresent;
50import static com.google.auto.common.Visibility.PRIVATE;
51import static com.google.auto.common.Visibility.PUBLIC;
52import static com.google.auto.common.Visibility.effectiveVisibilityOfElement;
53import static com.google.common.collect.Iterables.any;
54import static dagger.internal.codegen.ConfigurationAnnotations.getModuleIncludes;
55import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_WITH_SAME_NAME;
56import static dagger.internal.codegen.ErrorMessages.METHOD_OVERRIDES_PROVIDES_METHOD;
57import static dagger.internal.codegen.ErrorMessages.MODULES_WITH_TYPE_PARAMS_MUST_BE_ABSTRACT;
58import static dagger.internal.codegen.ErrorMessages.PROVIDES_METHOD_OVERRIDES_ANOTHER;
59import static dagger.internal.codegen.ErrorMessages.REFERENCED_MODULES_MUST_NOT_BE_ABSTRACT;
60import static dagger.internal.codegen.ErrorMessages.REFERENCED_MODULE_MUST_NOT_HAVE_TYPE_PARAMS;
61import static dagger.internal.codegen.ErrorMessages.REFERENCED_MODULE_NOT_ANNOTATED;
62import static javax.lang.model.element.Modifier.ABSTRACT;
63
64/**
65 * A {@linkplain ValidationReport validator} for {@link Module}s or {@link ProducerModule}s.
66 *
67 * @author Gregory Kick
68 * @since 2.0
69 */
70final class ModuleValidator {
71  private final Types types;
72  private final Elements elements;
73  private final Class<? extends Annotation> moduleClass;
74  private final ImmutableList<Class<? extends Annotation>> includedModuleClasses;
75  private final Class<? extends Annotation> methodClass;
76  private final MethodSignatureFormatter methodSignatureFormatter;
77
78  ModuleValidator(
79      Types types,
80      Elements elements,
81      MethodSignatureFormatter methodSignatureFormatter,
82      Class<? extends Annotation> moduleClass,
83      ImmutableList<Class<? extends Annotation>> includedModuleClasses,
84      Class<? extends Annotation> methodClass) {
85    this.types = types;
86    this.elements = elements;
87    this.moduleClass = moduleClass;
88    this.includedModuleClasses = includedModuleClasses;
89    this.methodClass = methodClass;
90    this.methodSignatureFormatter = methodSignatureFormatter;
91  }
92
93  ValidationReport<TypeElement> validate(final TypeElement subject) {
94    final ValidationReport.Builder<TypeElement> builder = ValidationReport.about(subject);
95
96    List<ExecutableElement> moduleMethods = ElementFilter.methodsIn(subject.getEnclosedElements());
97    ListMultimap<String, ExecutableElement> allMethodsByName = ArrayListMultimap.create();
98    ListMultimap<String, ExecutableElement> bindingMethodsByName = ArrayListMultimap.create();
99    for (ExecutableElement moduleMethod : moduleMethods) {
100      if (isAnnotationPresent(moduleMethod, methodClass)) {
101        bindingMethodsByName.put(moduleMethod.getSimpleName().toString(), moduleMethod);
102      }
103      allMethodsByName.put(moduleMethod.getSimpleName().toString(), moduleMethod);
104    }
105
106    validateModuleVisibility(subject, builder);
107    validateMethodsWithSameName(builder, bindingMethodsByName);
108    if (subject.getKind() != ElementKind.INTERFACE) {
109      validateProvidesOverrides(subject, builder, allMethodsByName, bindingMethodsByName);
110    }
111    validateModifiers(subject, builder);
112    validateReferencedModules(subject, builder);
113
114    // TODO(gak): port the dagger 1 module validation?
115    return builder.build();
116  }
117
118  private void validateModifiers(
119      TypeElement subject, ValidationReport.Builder<TypeElement> builder) {
120    // This coupled with the check for abstract modules in ComponentValidator guarantees that
121    // only modules without type parameters are referenced from @Component(modules={...}).
122    if (!subject.getTypeParameters().isEmpty() && !subject.getModifiers().contains(ABSTRACT)) {
123      builder.addError(MODULES_WITH_TYPE_PARAMS_MUST_BE_ABSTRACT, subject);
124    }
125  }
126
127  private void validateMethodsWithSameName(
128      ValidationReport.Builder<TypeElement> builder,
129      ListMultimap<String, ExecutableElement> bindingMethodsByName) {
130    for (Entry<String, Collection<ExecutableElement>> entry :
131        bindingMethodsByName.asMap().entrySet()) {
132      if (entry.getValue().size() > 1) {
133        for (ExecutableElement offendingMethod : entry.getValue()) {
134          builder.addError(
135              String.format(BINDING_METHOD_WITH_SAME_NAME, methodClass.getSimpleName()),
136              offendingMethod);
137        }
138      }
139    }
140  }
141
142  private void validateReferencedModules(
143      TypeElement subject, ValidationReport.Builder<TypeElement> builder) {
144    // Validate that all the modules we include are valid for inclusion.
145    AnnotationMirror mirror = getAnnotationMirror(subject, moduleClass).get();
146    ImmutableList<TypeMirror> includedTypes = getModuleIncludes(mirror);
147    validateReferencedModules(subject,  builder, includedTypes);
148  }
149
150  /**
151   * Used by {@link ModuleValidator} & {@link ComponentValidator} to validate referenced modules.
152   */
153  void validateReferencedModules(
154      final TypeElement subject,
155      final ValidationReport.Builder<TypeElement> builder,
156      ImmutableList<TypeMirror> includedTypes) {
157    for (TypeMirror includedType : includedTypes) {
158      includedType.accept(
159          new SimpleTypeVisitor6<Void, Void>() {
160            @Override
161            protected Void defaultAction(TypeMirror mirror, Void p) {
162              builder.addError(mirror + " is not a valid module type.", subject);
163              return null;
164            }
165
166            @Override
167            public Void visitDeclared(DeclaredType t, Void p) {
168              final TypeElement element = MoreElements.asType(t.asElement());
169              if (!t.getTypeArguments().isEmpty()) {
170                builder.addError(
171                    String.format(
172                        REFERENCED_MODULE_MUST_NOT_HAVE_TYPE_PARAMS, element.getQualifiedName()),
173                    subject);
174              }
175              boolean isIncludedModule =
176                  any(
177                      includedModuleClasses,
178                      new Predicate<Class<? extends Annotation>>() {
179                        @Override
180                        public boolean apply(Class<? extends Annotation> otherClass) {
181                          return MoreElements.isAnnotationPresent(element, otherClass);
182                        }
183                      });
184              if (!isIncludedModule) {
185                builder.addError(
186                    String.format(
187                        REFERENCED_MODULE_NOT_ANNOTATED,
188                        element.getQualifiedName(),
189                        (includedModuleClasses.size() > 1 ? "one of " : "")
190                            + Joiner.on(", ")
191                                .join(
192                                    FluentIterable.from(includedModuleClasses)
193                                        .transform(
194                                            new Function<Class<? extends Annotation>, String>() {
195                                              @Override
196                                              public String apply(
197                                                  Class<? extends Annotation> otherClass) {
198                                                return "@" + otherClass.getSimpleName();
199                                              }
200                                            }))),
201                    subject);
202              }
203              if (element.getModifiers().contains(ABSTRACT)) {
204                builder.addError(
205                    String.format(
206                        REFERENCED_MODULES_MUST_NOT_BE_ABSTRACT, element.getQualifiedName()),
207                    subject);
208              }
209              return null;
210            }
211          },
212          null);
213    }
214  }
215
216  private void validateProvidesOverrides(
217      TypeElement subject,
218      ValidationReport.Builder<TypeElement> builder,
219      ListMultimap<String, ExecutableElement> allMethodsByName,
220      ListMultimap<String, ExecutableElement> bindingMethodsByName) {
221    // For every @Provides method, confirm it overrides nothing *and* nothing overrides it.
222    // Consider the following hierarchy:
223    // class Parent {
224    //    @Provides Foo a() {}
225    //    @Provides Foo b() {}
226    //    Foo c() {}
227    // }
228    // class Child extends Parent {
229    //    @Provides Foo a() {}
230    //    Foo b() {}
231    //    @Provides Foo c() {}
232    // }
233    // In each of those cases, we want to fail.  "a" is clear, "b" because Child is overriding
234    // a method marked @Provides in Parent, and "c" because Child is defining an @Provides
235    // method that overrides Parent.
236    TypeElement currentClass = subject;
237    TypeMirror objectType = elements.getTypeElement(Object.class.getCanonicalName()).asType();
238    // We keep track of methods that failed so we don't spam with multiple failures.
239    Set<ExecutableElement> failedMethods = Sets.newHashSet();
240    while (!types.isSameType(currentClass.getSuperclass(), objectType)) {
241      currentClass = MoreElements.asType(types.asElement(currentClass.getSuperclass()));
242      List<ExecutableElement> superclassMethods =
243          ElementFilter.methodsIn(currentClass.getEnclosedElements());
244      for (ExecutableElement superclassMethod : superclassMethods) {
245        String name = superclassMethod.getSimpleName().toString();
246        // For each method in the superclass, confirm our @Provides methods don't override it
247        for (ExecutableElement providesMethod : bindingMethodsByName.get(name)) {
248          if (!failedMethods.contains(providesMethod)
249              && elements.overrides(providesMethod, superclassMethod, subject)) {
250            failedMethods.add(providesMethod);
251            builder.addError(
252                String.format(
253                    PROVIDES_METHOD_OVERRIDES_ANOTHER,
254                    methodClass.getSimpleName(),
255                    methodSignatureFormatter.format(superclassMethod)),
256                providesMethod);
257          }
258        }
259        // For each @Provides method in superclass, confirm our methods don't override it.
260        if (isAnnotationPresent(superclassMethod, methodClass)) {
261          for (ExecutableElement method : allMethodsByName.get(name)) {
262            if (!failedMethods.contains(method)
263                && elements.overrides(method, superclassMethod, subject)) {
264              failedMethods.add(method);
265              builder.addError(
266                  String.format(
267                      METHOD_OVERRIDES_PROVIDES_METHOD,
268                      methodClass.getSimpleName(),
269                      methodSignatureFormatter.format(superclassMethod)),
270                  method);
271            }
272          }
273        }
274        allMethodsByName.put(superclassMethod.getSimpleName().toString(), superclassMethod);
275      }
276    }
277  }
278
279  private void validateModuleVisibility(final TypeElement moduleElement,
280      final ValidationReport.Builder<?> reportBuilder) {
281    Visibility moduleVisibility = Visibility.ofElement(moduleElement);
282    if (moduleVisibility.equals(PRIVATE)) {
283      reportBuilder.addError("Modules cannot be private.", moduleElement);
284    } else if (effectiveVisibilityOfElement(moduleElement).equals(PRIVATE)) {
285      reportBuilder.addError("Modules cannot be enclosed in private types.", moduleElement);
286    }
287
288    switch (moduleElement.getNestingKind()) {
289      case ANONYMOUS:
290        throw new IllegalStateException("Can't apply @Module to an anonymous class");
291      case LOCAL:
292        throw new IllegalStateException("Local classes shouldn't show up in the processor");
293      case MEMBER:
294      case TOP_LEVEL:
295        if (moduleVisibility.equals(PUBLIC)) {
296          ImmutableSet<Element> nonPublicModules = FluentIterable.from(getModuleIncludes(
297              getAnnotationMirror(moduleElement, moduleClass).get()))
298                  .transform(new Function<TypeMirror, Element>() {
299                    @Override public Element apply(TypeMirror input) {
300                      return types.asElement(input);
301                    }
302                  })
303                  .filter(new Predicate<Element>() {
304                    @Override public boolean apply(Element input) {
305                      return effectiveVisibilityOfElement(input).compareTo(PUBLIC) < 0;
306                    }
307                  })
308                  .toSet();
309          if (!nonPublicModules.isEmpty()) {
310            reportBuilder.addError(
311                String.format(
312                    "This module is public, but it includes non-public "
313                        + "(or effectively non-public) modules. "
314                        + "Either reduce the visibility of this module or make %s public.",
315                    formatListForErrorMessage(nonPublicModules.asList())),
316                moduleElement);
317          }
318        }
319        break;
320      default:
321        throw new AssertionError();
322    }
323  }
324
325  private static String formatListForErrorMessage(List<?> things) {
326    switch (things.size()) {
327      case 0:
328        return "";
329      case 1:
330        return things.get(0).toString();
331      default:
332        StringBuilder output = new StringBuilder();
333        Joiner.on(", ").appendTo(output, things.subList(0, things.size() - 1));
334        output.append(" and ").append(things.get(things.size() - 1));
335        return output.toString();
336    }
337  }
338}
339