/*
 * Copyright (C) 2014 The Dagger Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dagger.internal.codegen.validation;

import static com.google.auto.common.AnnotationMirrors.getAnnotatedAnnotations;
import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static com.google.auto.common.MoreTypes.asTypeElement;
import static com.google.auto.common.Visibility.PRIVATE;
import static com.google.auto.common.Visibility.PUBLIC;
import static com.google.auto.common.Visibility.effectiveVisibilityOfElement;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.base.ComponentAnnotation.componentAnnotation;
import static dagger.internal.codegen.base.ComponentAnnotation.isComponentAnnotation;
import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation;
import static dagger.internal.codegen.base.ModuleAnnotation.isModuleAnnotation;
import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation;
import static dagger.internal.codegen.base.MoreAnnotationMirrors.simpleName;
import static dagger.internal.codegen.base.MoreAnnotationValues.asType;
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.getCreatorAnnotations;
import static dagger.internal.codegen.binding.ConfigurationAnnotations.getSubcomponentCreator;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror;
import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent;
import static java.util.stream.Collectors.joining;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.STATIC;
import static javax.lang.model.util.ElementFilter.methodsIn;

import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.common.Visibility;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.google.errorprone.annotations.FormatMethod;
import dagger.Module;
import dagger.Subcomponent;
import dagger.internal.codegen.base.ModuleAnnotation;
import dagger.internal.codegen.binding.BindingGraphFactory;
import dagger.internal.codegen.binding.ComponentCreatorAnnotation;
import dagger.internal.codegen.binding.ComponentDescriptorFactory;
import dagger.internal.codegen.binding.MethodSignatureFormatter;
import dagger.internal.codegen.binding.ModuleKind;
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
import dagger.internal.codegen.langmodel.DaggerElements;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.model.BindingGraph;
import dagger.producers.ProducerModule;
import dagger.producers.ProductionSubcomponent;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Scope;
import javax.inject.Singleton;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleAnnotationValueVisitor8;
import javax.lang.model.util.SimpleTypeVisitor8;

/** A {@linkplain ValidationReport validator} for {@link Module}s or {@link ProducerModule}s. */
@Singleton
public final class ModuleValidator {
  private static final ImmutableSet<Class<? extends Annotation>> SUBCOMPONENT_TYPES =
      ImmutableSet.of(Subcomponent.class, ProductionSubcomponent.class);
  private static final ImmutableSet<Class<? extends Annotation>> SUBCOMPONENT_CREATOR_TYPES =
      ImmutableSet.of(
          Subcomponent.Builder.class,
          Subcomponent.Factory.class,
          ProductionSubcomponent.Builder.class,
          ProductionSubcomponent.Factory.class);
  private static final Optional<Class<?>> ANDROID_PROCESSOR;
  private static final String CONTRIBUTES_ANDROID_INJECTOR_NAME =
      "dagger.android.ContributesAndroidInjector";
  private static final String ANDROID_PROCESSOR_NAME = "dagger.android.processor.AndroidProcessor";

  static {
    Class<?> clazz;
    try {
      clazz = Class.forName(ANDROID_PROCESSOR_NAME, false, ModuleValidator.class.getClassLoader());
    } catch (ClassNotFoundException ignored) {
      clazz = null;
    }
    ANDROID_PROCESSOR = Optional.ofNullable(clazz);
  }

  private final DaggerTypes types;
  private final DaggerElements elements;
  private final AnyBindingMethodValidator anyBindingMethodValidator;
  private final MethodSignatureFormatter methodSignatureFormatter;
  private final ComponentDescriptorFactory componentDescriptorFactory;
  private final BindingGraphFactory bindingGraphFactory;
  private final BindingGraphValidator bindingGraphValidator;
  private final KotlinMetadataUtil metadataUtil;
  private final Map<TypeElement, ValidationReport<TypeElement>> cache = new HashMap<>();
  private final Set<TypeElement> knownModules = new HashSet<>();

  @Inject
  ModuleValidator(
      DaggerTypes types,
      DaggerElements elements,
      AnyBindingMethodValidator anyBindingMethodValidator,
      MethodSignatureFormatter methodSignatureFormatter,
      ComponentDescriptorFactory componentDescriptorFactory,
      BindingGraphFactory bindingGraphFactory,
      BindingGraphValidator bindingGraphValidator,
      KotlinMetadataUtil metadataUtil) {
    this.types = types;
    this.elements = elements;
    this.anyBindingMethodValidator = anyBindingMethodValidator;
    this.methodSignatureFormatter = methodSignatureFormatter;
    this.componentDescriptorFactory = componentDescriptorFactory;
    this.bindingGraphFactory = bindingGraphFactory;
    this.bindingGraphValidator = bindingGraphValidator;
    this.metadataUtil = metadataUtil;
  }

  /**
   * Adds {@code modules} to the set of module types that will be validated during this compilation
   * step. If a component or module includes a module that is not in this set, that included module
   * is assumed to be valid because it was processed in a previous compilation step. If it were
   * invalid, that previous compilation step would have failed and blocked this one.
   *
   * <p>This logic depends on this method being called before {@linkplain #validate(TypeElement)
   * validating} any module or {@linkplain #validateReferencedModules(TypeElement, AnnotationMirror,
   * ImmutableSet, Set) component}.
   */
  public void addKnownModules(Collection<TypeElement> modules) {
    knownModules.addAll(modules);
  }

  /** Returns a validation report for a module type. */
  public ValidationReport<TypeElement> validate(TypeElement module) {
    return validate(module, new HashSet<>());
  }

  private ValidationReport<TypeElement> validate(
      TypeElement module, Set<TypeElement> visitedModules) {
    if (visitedModules.add(module)) {
      return reentrantComputeIfAbsent(cache, module, m -> validateUncached(module, visitedModules));
    }
    return ValidationReport.about(module).build();
  }

  private ValidationReport<TypeElement> validateUncached(
      TypeElement module, Set<TypeElement> visitedModules) {
    ValidationReport.Builder<TypeElement> builder = ValidationReport.about(module);
    ModuleKind moduleKind = ModuleKind.forAnnotatedElement(module).get();
    TypeElement contributesAndroidInjectorElement =
        elements.getTypeElement(CONTRIBUTES_ANDROID_INJECTOR_NAME);
    TypeMirror contributesAndroidInjector =
        contributesAndroidInjectorElement != null
            ? contributesAndroidInjectorElement.asType()
            : null;
    List<ExecutableElement> moduleMethods = methodsIn(module.getEnclosedElements());
    List<ExecutableElement> bindingMethods = new ArrayList<>();
    for (ExecutableElement moduleMethod : moduleMethods) {
      if (anyBindingMethodValidator.isBindingMethod(moduleMethod)) {
        builder.addSubreport(anyBindingMethodValidator.validate(moduleMethod));
        bindingMethods.add(moduleMethod);
      }

      for (AnnotationMirror annotation : moduleMethod.getAnnotationMirrors()) {
        if (!ANDROID_PROCESSOR.isPresent()
            && MoreTypes.equivalence()
                .equivalent(contributesAndroidInjector, annotation.getAnnotationType())) {
          builder.addSubreport(
              ValidationReport.about(moduleMethod)
                  .addError(
                      String.format(
                          "@%s was used, but %s was not found on the processor path",
                          CONTRIBUTES_ANDROID_INJECTOR_NAME, ANDROID_PROCESSOR_NAME))
                  .build());
          break;
        }
      }
    }

    if (bindingMethods.stream()
        .map(ModuleMethodKind::ofMethod)
        .collect(toImmutableSet())
        .containsAll(
            EnumSet.of(ModuleMethodKind.ABSTRACT_DECLARATION, ModuleMethodKind.INSTANCE_BINDING))) {
      builder.addError(
          String.format(
              "A @%s may not contain both non-static and abstract binding methods",
              moduleKind.annotation().getSimpleName()));
    }

    validateModuleVisibility(module, moduleKind, builder);

    ImmutableListMultimap<Name, ExecutableElement> bindingMethodsByName =
        Multimaps.index(bindingMethods, ExecutableElement::getSimpleName);

    validateMethodsWithSameName(builder, bindingMethodsByName);
    if (module.getKind() != ElementKind.INTERFACE) {
      validateBindingMethodOverrides(
          module,
          builder,
          Multimaps.index(moduleMethods, ExecutableElement::getSimpleName),
          bindingMethodsByName);
    }
    validateModifiers(module, builder);
    validateReferencedModules(module, moduleKind, visitedModules, builder);
    validateReferencedSubcomponents(module, moduleKind, builder);
    validateNoScopeAnnotationsOnModuleElement(module, moduleKind, builder);
    validateSelfCycles(module, builder);
    if (metadataUtil.hasEnclosedCompanionObject(module)) {
      validateCompanionModule(module, builder);
    }

    if (builder.build().isClean()
        && bindingGraphValidator.shouldDoFullBindingGraphValidation(module)) {
      validateModuleBindings(module, builder);
    }

    return builder.build();
  }

  private void validateReferencedSubcomponents(
      final TypeElement subject,
      ModuleKind moduleKind,
      final ValidationReport.Builder<TypeElement> builder) {
    // TODO(ronshapiro): use validateTypesAreDeclared when it is checked in
    ModuleAnnotation moduleAnnotation = moduleAnnotation(moduleKind.getModuleAnnotation(subject));
    for (AnnotationValue subcomponentAttribute :
        moduleAnnotation.subcomponentsAsAnnotationValues()) {
      asType(subcomponentAttribute)
          .accept(
              new SimpleTypeVisitor8<Void, Void>() {
                @Override
                protected Void defaultAction(TypeMirror e, Void aVoid) {
                  builder.addError(
                      e + " is not a valid subcomponent type",
                      subject,
                      moduleAnnotation.annotation(),
                      subcomponentAttribute);
                  return null;
                }

                @Override
                public Void visitDeclared(DeclaredType declaredType, Void aVoid) {
                  TypeElement attributeType = asTypeElement(declaredType);
                  if (isAnyAnnotationPresent(attributeType, SUBCOMPONENT_TYPES)) {
                    validateSubcomponentHasBuilder(
                        attributeType, moduleAnnotation.annotation(), builder);
                  } else {
                    builder.addError(
                        isAnyAnnotationPresent(attributeType, SUBCOMPONENT_CREATOR_TYPES)
                            ? moduleSubcomponentsIncludesCreator(attributeType)
                            : moduleSubcomponentsIncludesNonSubcomponent(attributeType),
                        subject,
                        moduleAnnotation.annotation(),
                        subcomponentAttribute);
                  }

                  return null;
                }
              },
              null);
    }
  }

  private static String moduleSubcomponentsIncludesNonSubcomponent(TypeElement notSubcomponent) {
    return notSubcomponent.getQualifiedName()
        + " is not a @Subcomponent or @ProductionSubcomponent";
  }

  private static String moduleSubcomponentsIncludesCreator(
      TypeElement moduleSubcomponentsAttribute) {
    TypeElement subcomponentType =
        MoreElements.asType(moduleSubcomponentsAttribute.getEnclosingElement());
    ComponentCreatorAnnotation creatorAnnotation =
        getOnlyElement(getCreatorAnnotations(moduleSubcomponentsAttribute));
    return String.format(
        "%s is a @%s.%s. Did you mean to use %s?",
        moduleSubcomponentsAttribute.getQualifiedName(),
        subcomponentAnnotation(subcomponentType).get().simpleName(),
        creatorAnnotation.creatorKind().typeName(),
        subcomponentType.getQualifiedName());
  }

  private static void validateSubcomponentHasBuilder(
      TypeElement subcomponentAttribute,
      AnnotationMirror moduleAnnotation,
      ValidationReport.Builder<TypeElement> builder) {
    if (getSubcomponentCreator(subcomponentAttribute).isPresent()) {
      return;
    }
    builder.addError(
        moduleSubcomponentsDoesntHaveCreator(subcomponentAttribute, moduleAnnotation),
        builder.getSubject(),
        moduleAnnotation);
  }

  private static String moduleSubcomponentsDoesntHaveCreator(
      TypeElement subcomponent, AnnotationMirror moduleAnnotation) {
    return String.format(
        "%1$s doesn't have a @%2$s.Builder or @%2$s.Factory, which is required when used with "
            + "@%3$s.subcomponents",
        subcomponent.getQualifiedName(),
        subcomponentAnnotation(subcomponent).get().simpleName(),
        simpleName(moduleAnnotation));
  }

  enum ModuleMethodKind {
    ABSTRACT_DECLARATION,
    INSTANCE_BINDING,
    STATIC_BINDING,
    ;

    static ModuleMethodKind ofMethod(ExecutableElement moduleMethod) {
      if (moduleMethod.getModifiers().contains(STATIC)) {
        return STATIC_BINDING;
      } else if (moduleMethod.getModifiers().contains(ABSTRACT)) {
        return ABSTRACT_DECLARATION;
      } else {
        return INSTANCE_BINDING;
      }
    }
  }

  private void validateModifiers(
      TypeElement subject, ValidationReport.Builder<TypeElement> builder) {
    // This coupled with the check for abstract modules in ComponentValidator guarantees that
    // only modules without type parameters are referenced from @Component(modules={...}).
    if (!subject.getTypeParameters().isEmpty() && !subject.getModifiers().contains(ABSTRACT)) {
      builder.addError("Modules with type parameters must be abstract", subject);
    }
  }

  private void validateMethodsWithSameName(
      ValidationReport.Builder<TypeElement> builder,
      ListMultimap<Name, ExecutableElement> bindingMethodsByName) {
    for (Entry<Name, Collection<ExecutableElement>> entry :
        bindingMethodsByName.asMap().entrySet()) {
      if (entry.getValue().size() > 1) {
        for (ExecutableElement offendingMethod : entry.getValue()) {
          builder.addError(
              String.format(
                  "Cannot have more than one binding method with the same name in a single module"),
              offendingMethod);
        }
      }
    }
  }

  private void validateReferencedModules(
      TypeElement subject,
      ModuleKind moduleKind,
      Set<TypeElement> visitedModules,
      ValidationReport.Builder<TypeElement> builder) {
    // Validate that all the modules we include are valid for inclusion.
    AnnotationMirror mirror = moduleKind.getModuleAnnotation(subject);
    builder.addSubreport(
        validateReferencedModules(
            subject, mirror, moduleKind.legalIncludedModuleKinds(), visitedModules));
  }

  /**
   * Validates modules included in a given module or installed in a given component.
   *
   * <p>Checks that the referenced modules are non-generic types annotated with {@code @Module} or
   * {@code @ProducerModule}.
   *
   * <p>If the referenced module is in the {@linkplain #addKnownModules(Collection) known modules
   * set} and has errors, reports an error at that module's inclusion.
   *
   * @param annotatedType the annotated module or component
   * @param annotation the annotation specifying the referenced modules ({@code @Component},
   *     {@code @ProductionComponent}, {@code @Subcomponent}, {@code @ProductionSubcomponent},
   *     {@code @Module}, or {@code @ProducerModule})
   * @param validModuleKinds the module kinds that the annotated type is permitted to include
   */
  ValidationReport<TypeElement> validateReferencedModules(
      TypeElement annotatedType,
      AnnotationMirror annotation,
      ImmutableSet<ModuleKind> validModuleKinds,
      Set<TypeElement> visitedModules) {
    ValidationReport.Builder<TypeElement> subreport = ValidationReport.about(annotatedType);
    ImmutableSet<? extends Class<? extends Annotation>> validModuleAnnotations =
        validModuleKinds.stream().map(ModuleKind::annotation).collect(toImmutableSet());

    for (AnnotationValue includedModule : getModules(annotation)) {
      asType(includedModule)
          .accept(
              new SimpleTypeVisitor8<Void, Void>() {
                @Override
                protected Void defaultAction(TypeMirror mirror, Void p) {
                  reportError("%s is not a valid module type.", mirror);
                  return null;
                }

                @Override
                public Void visitDeclared(DeclaredType t, Void p) {
                  TypeElement module = MoreElements.asType(t.asElement());
                  if (!t.getTypeArguments().isEmpty()) {
                    reportError(
                        "%s is listed as a module, but has type parameters",
                        module.getQualifiedName());
                  }
                  if (!isAnyAnnotationPresent(module, validModuleAnnotations)) {
                    reportError(
                        "%s is listed as a module, but is not annotated with %s",
                        module.getQualifiedName(),
                        (validModuleAnnotations.size() > 1 ? "one of " : "")
                            + validModuleAnnotations.stream()
                                .map(otherClass -> "@" + otherClass.getSimpleName())
                                .collect(joining(", ")));
                  } else if (knownModules.contains(module)
                      && !validate(module, visitedModules).isClean()) {
                    reportError("%s has errors", module.getQualifiedName());
                  }
                  if (metadataUtil.isCompanionObjectClass(module)) {
                    reportError(
                        "%s is listed as a module, but it is a companion object class. "
                            + "Add @Module to the enclosing class and reference that instead.",
                        module.getQualifiedName());
                  }
                  return null;
                }

                @FormatMethod
                private void reportError(String format, Object... args) {
                  subreport.addError(
                      String.format(format, args), annotatedType, annotation, includedModule);
                }
              },
              null);
    }
    return subreport.build();
  }

  private static ImmutableList<AnnotationValue> getModules(AnnotationMirror annotation) {
    if (isModuleAnnotation(annotation)) {
      return moduleAnnotation(annotation).includesAsAnnotationValues();
    }
    if (isComponentAnnotation(annotation)) {
      return componentAnnotation(annotation).moduleValues();
    }
    throw new IllegalArgumentException(String.format("unsupported annotation: %s", annotation));
  }

  private void validateBindingMethodOverrides(
      TypeElement subject,
      ValidationReport.Builder<TypeElement> builder,
      ImmutableListMultimap<Name, ExecutableElement> moduleMethodsByName,
      ImmutableListMultimap<Name, ExecutableElement> bindingMethodsByName) {
    // For every binding method, confirm it overrides nothing *and* nothing overrides it.
    // Consider the following hierarchy:
    // class Parent {
    //    @Provides Foo a() {}
    //    @Provides Foo b() {}
    //    Foo c() {}
    // }
    // class Child extends Parent {
    //    @Provides Foo a() {}
    //    Foo b() {}
    //    @Provides Foo c() {}
    // }
    // In each of those cases, we want to fail.  "a" is clear, "b" because Child is overriding
    // a binding method in Parent, and "c" because Child is defining a binding method that overrides
    // Parent.
    TypeElement currentClass = subject;
    TypeMirror objectType = elements.getTypeElement(Object.class).asType();
    // We keep track of methods that failed so we don't spam with multiple failures.
    Set<ExecutableElement> failedMethods = Sets.newHashSet();
    ListMultimap<Name, ExecutableElement> allMethodsByName =
        MultimapBuilder.hashKeys().arrayListValues().build(moduleMethodsByName);

    while (!types.isSameType(currentClass.getSuperclass(), objectType)) {
      currentClass = MoreElements.asType(types.asElement(currentClass.getSuperclass()));
      List<ExecutableElement> superclassMethods = methodsIn(currentClass.getEnclosedElements());
      for (ExecutableElement superclassMethod : superclassMethods) {
        Name name = superclassMethod.getSimpleName();
        // For each method in the superclass, confirm our binding methods don't override it
        for (ExecutableElement bindingMethod : bindingMethodsByName.get(name)) {
          if (failedMethods.add(bindingMethod)
              && elements.overrides(bindingMethod, superclassMethod, subject)) {
            builder.addError(
                String.format(
                    "Binding methods may not override another method. Overrides: %s",
                    methodSignatureFormatter.format(superclassMethod)),
                bindingMethod);
          }
        }
        // For each binding method in superclass, confirm our methods don't override it.
        if (anyBindingMethodValidator.isBindingMethod(superclassMethod)) {
          for (ExecutableElement method : allMethodsByName.get(name)) {
            if (failedMethods.add(method)
                && elements.overrides(method, superclassMethod, subject)) {
              builder.addError(
                  String.format(
                      "Binding methods may not be overridden in modules. Overrides: %s",
                      methodSignatureFormatter.format(superclassMethod)),
                  method);
            }
          }
        }
        allMethodsByName.put(superclassMethod.getSimpleName(), superclassMethod);
      }
    }
  }

  private void validateModuleVisibility(
      final TypeElement moduleElement,
      ModuleKind moduleKind,
      final ValidationReport.Builder<?> reportBuilder) {
    ModuleAnnotation moduleAnnotation =
        moduleAnnotation(getAnnotationMirror(moduleElement, moduleKind.annotation()).get());
    Visibility moduleVisibility = Visibility.ofElement(moduleElement);
    Visibility moduleEffectiveVisibility = effectiveVisibilityOfElement(moduleElement);
    if (moduleVisibility.equals(PRIVATE)) {
      reportBuilder.addError("Modules cannot be private.", moduleElement);
    } else if (moduleEffectiveVisibility.equals(PRIVATE)) {
      reportBuilder.addError("Modules cannot be enclosed in private types.", moduleElement);
    }

    switch (moduleElement.getNestingKind()) {
      case ANONYMOUS:
        throw new IllegalStateException("Can't apply @Module to an anonymous class");
      case LOCAL:
        throw new IllegalStateException("Local classes shouldn't show up in the processor");
      case MEMBER:
      case TOP_LEVEL:
        if (moduleEffectiveVisibility.equals(PUBLIC)) {
          ImmutableSet<TypeElement> invalidVisibilityIncludes =
              getModuleIncludesWithInvalidVisibility(moduleAnnotation);
          if (!invalidVisibilityIncludes.isEmpty()) {
            reportBuilder.addError(
                String.format(
                    "This module is public, but it includes non-public (or effectively non-public) "
                        + "modules (%s) that have non-static, non-abstract binding methods. Either "
                        + "reduce the visibility of this module, make the included modules "
                        + "public, or make all of the binding methods on the included modules "
                        + "abstract or static.",
                    formatListForErrorMessage(invalidVisibilityIncludes.asList())),
                moduleElement);
          }
        }
    }
  }

  private ImmutableSet<TypeElement> getModuleIncludesWithInvalidVisibility(
      ModuleAnnotation moduleAnnotation) {
    return moduleAnnotation.includes().stream()
        .filter(include -> !effectiveVisibilityOfElement(include).equals(PUBLIC))
        .filter(this::requiresModuleInstance)
        .collect(toImmutableSet());
  }

  /**
   * Returns {@code true} if a module instance is needed for any of the binding methods on the given
   * {@code module}. This is the case when the module has any binding methods that are neither
   * {@code abstract} nor {@code static}. Alternatively, if the module is a Kotlin Object then the
   * binding methods are considered {@code static}, requiring no module instance.
   */
  private boolean requiresModuleInstance(TypeElement module) {
    // Note elements.getAllMembers(module) rather than module.getEnclosedElements() here: we need to
    // include binding methods declared in supertypes because unlike most other validations being
    // done in this class, which assume that supertype binding methods will be validated in a
    // separate call to the validator since the supertype itself must be a @Module, we need to look
    // at all the binding methods in the module's type hierarchy here.
    boolean isKotlinObject =
        metadataUtil.isObjectClass(module) || metadataUtil.isCompanionObjectClass(module);
    if (isKotlinObject) {
      return false;
    }
    return methodsIn(elements.getAllMembers(module)).stream()
        .filter(anyBindingMethodValidator::isBindingMethod)
        .map(ExecutableElement::getModifiers)
        .anyMatch(modifiers -> !modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC));
  }

  private void validateNoScopeAnnotationsOnModuleElement(
      TypeElement module, ModuleKind moduleKind, ValidationReport.Builder<TypeElement> report) {
    for (AnnotationMirror scope : getAnnotatedAnnotations(module, Scope.class)) {
      report.addError(
          String.format(
              "@%ss cannot be scoped. Did you mean to scope a method instead?",
              moduleKind.annotation().getSimpleName()),
          module,
          scope);
    }
  }

  private void validateSelfCycles(
      TypeElement module, ValidationReport.Builder<TypeElement> builder) {
    ModuleAnnotation moduleAnnotation = moduleAnnotation(module).get();
    moduleAnnotation
        .includesAsAnnotationValues()
        .forEach(
            value ->
                value.accept(
                    new SimpleAnnotationValueVisitor8<Void, Void>() {
                      @Override
                      public Void visitType(TypeMirror includedModule, Void aVoid) {
                        if (MoreTypes.equivalence().equivalent(module.asType(), includedModule)) {
                          String moduleKind = moduleAnnotation.annotationName();
                          builder.addError(
                              String.format("@%s cannot include themselves.", moduleKind),
                              module,
                              moduleAnnotation.annotation(),
                              value);
                        }
                        return null;
                      }
                    },
                    null));
  }

  private void validateCompanionModule(
      TypeElement module, ValidationReport.Builder<TypeElement> builder) {
    checkArgument(metadataUtil.hasEnclosedCompanionObject(module));
    TypeElement companionModule = metadataUtil.getEnclosedCompanionObject(module);
    List<ExecutableElement> companionModuleMethods =
        methodsIn(companionModule.getEnclosedElements());
    List<ExecutableElement> companionBindingMethods = new ArrayList<>();
    for (ExecutableElement companionModuleMethod : companionModuleMethods) {
      if (anyBindingMethodValidator.isBindingMethod(companionModuleMethod)) {
        builder.addSubreport(anyBindingMethodValidator.validate(companionModuleMethod));
        companionBindingMethods.add(companionModuleMethod);
      }

      // On normal modules only overriding other binding methods is disallowed, but for companion
      // objects we are prohibiting any override. For this can rely on checking the @Override
      // annotation since the Kotlin compiler will always produce them for overriding methods.
      if (isAnnotationPresent(companionModuleMethod, Override.class)) {
        builder.addError(
            "Binding method in companion object may not override another method.",
            companionModuleMethod);
      }

      // TODO(danysantiago): Be strict about the usage of @JvmStatic, i.e. tell user to remove it.
    }

    ImmutableListMultimap<Name, ExecutableElement> bindingMethodsByName =
        Multimaps.index(companionBindingMethods, ExecutableElement::getSimpleName);
    validateMethodsWithSameName(builder, bindingMethodsByName);

    // If there are provision methods, then check the visibility. Companion objects are composed by
    // an inner class and a static field, it is not enough to check the visibility on the type
    // element or the field, therefore we check the metadata.
    if (!companionBindingMethods.isEmpty() && metadataUtil.isVisibilityPrivate(companionModule)) {
      builder.addError(
          "A Companion Module with binding methods cannot be private.", companionModule);
    }
  }

  private void validateModuleBindings(
      TypeElement module, ValidationReport.Builder<TypeElement> report) {
    BindingGraph bindingGraph =
        bindingGraphFactory.create(
                componentDescriptorFactory.moduleComponentDescriptor(module), true)
            .topLevelBindingGraph();
    if (!bindingGraphValidator.isValid(bindingGraph)) {
      // Since the validator uses a DiagnosticReporter to report errors, the ValdiationReport won't
      // have any Items for them. We have to tell the ValidationReport that some errors were
      // reported for the subject.
      report.markDirty();
    }
  }

  private static String formatListForErrorMessage(List<?> things) {
    switch (things.size()) {
      case 0:
        return "";
      case 1:
        return things.get(0).toString();
      default:
        StringBuilder output = new StringBuilder();
        Joiner.on(", ").appendTo(output, things.subList(0, things.size() - 1));
        output.append(" and ").append(things.get(things.size() - 1));
        return output.toString();
    }
  }
}
