Home | History | Annotate | Download | only in codegen
      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 
     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