• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
16 package dagger.internal.codegen;
17 
18 import com.google.auto.common.MoreElements;
19 import com.google.auto.common.Visibility;
20 import com.google.common.base.Function;
21 import com.google.common.base.Joiner;
22 import com.google.common.base.Predicate;
23 import com.google.common.collect.ArrayListMultimap;
24 import com.google.common.collect.FluentIterable;
25 import com.google.common.collect.ImmutableList;
26 import com.google.common.collect.ImmutableSet;
27 import com.google.common.collect.ListMultimap;
28 import com.google.common.collect.Sets;
29 import dagger.Module;
30 import dagger.producers.ProducerModule;
31 import java.lang.annotation.Annotation;
32 import java.util.Collection;
33 import java.util.List;
34 import java.util.Map.Entry;
35 import java.util.Set;
36 import javax.lang.model.element.AnnotationMirror;
37 import javax.lang.model.element.Element;
38 import javax.lang.model.element.ElementKind;
39 import javax.lang.model.element.ExecutableElement;
40 import javax.lang.model.element.TypeElement;
41 import javax.lang.model.type.DeclaredType;
42 import javax.lang.model.type.TypeMirror;
43 import javax.lang.model.util.ElementFilter;
44 import javax.lang.model.util.Elements;
45 import javax.lang.model.util.SimpleTypeVisitor6;
46 import javax.lang.model.util.Types;
47 
48 import static com.google.auto.common.MoreElements.getAnnotationMirror;
49 import static com.google.auto.common.MoreElements.isAnnotationPresent;
50 import static com.google.auto.common.Visibility.PRIVATE;
51 import static com.google.auto.common.Visibility.PUBLIC;
52 import static com.google.auto.common.Visibility.effectiveVisibilityOfElement;
53 import static com.google.common.collect.Iterables.any;
54 import static dagger.internal.codegen.ConfigurationAnnotations.getModuleIncludes;
55 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_WITH_SAME_NAME;
56 import static dagger.internal.codegen.ErrorMessages.METHOD_OVERRIDES_PROVIDES_METHOD;
57 import static dagger.internal.codegen.ErrorMessages.MODULES_WITH_TYPE_PARAMS_MUST_BE_ABSTRACT;
58 import static dagger.internal.codegen.ErrorMessages.PROVIDES_METHOD_OVERRIDES_ANOTHER;
59 import static dagger.internal.codegen.ErrorMessages.REFERENCED_MODULES_MUST_NOT_BE_ABSTRACT;
60 import static dagger.internal.codegen.ErrorMessages.REFERENCED_MODULE_MUST_NOT_HAVE_TYPE_PARAMS;
61 import static dagger.internal.codegen.ErrorMessages.REFERENCED_MODULE_NOT_ANNOTATED;
62 import 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  */
70 final 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 
ModuleValidator( Types types, Elements elements, MethodSignatureFormatter methodSignatureFormatter, Class<? extends Annotation> moduleClass, ImmutableList<Class<? extends Annotation>> includedModuleClasses, Class<? extends Annotation> methodClass)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 
validate(final TypeElement subject)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 
validateModifiers( TypeElement subject, ValidationReport.Builder<TypeElement> builder)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 
validateMethodsWithSameName( ValidationReport.Builder<TypeElement> builder, ListMultimap<String, ExecutableElement> bindingMethodsByName)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 
validateReferencedModules( TypeElement subject, ValidationReport.Builder<TypeElement> builder)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    */
validateReferencedModules( final TypeElement subject, final ValidationReport.Builder<TypeElement> builder, ImmutableList<TypeMirror> includedTypes)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 
validateProvidesOverrides( TypeElement subject, ValidationReport.Builder<TypeElement> builder, ListMultimap<String, ExecutableElement> allMethodsByName, ListMultimap<String, ExecutableElement> bindingMethodsByName)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 
validateModuleVisibility(final TypeElement moduleElement, final ValidationReport.Builder<?> reportBuilder)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 
formatListForErrorMessage(List<?> things)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