/*
 * Copyright (C) 2015 Google, Inc.
 *
 * 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;

import com.google.common.collect.ImmutableList;
import com.google.testing.compile.JavaFileObjects;
import javax.tools.JavaFileObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import static com.google.common.truth.Truth.assertAbout;
import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;

/** Tests for {@link dagger.Component.Builder} */
@RunWith(JUnit4.class)
public class ComponentBuilderTest {

  private static final ErrorMessages.ComponentBuilderMessages MSGS =
      ErrorMessages.ComponentBuilderMessages.INSTANCE;

  @Test
  public void testEmptyBuilder() {
    JavaFileObject injectableTypeFile = JavaFileObjects.forSourceLines("test.SomeInjectableType",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "final class SomeInjectableType {",
        "  @Inject SomeInjectableType() {}",
        "}");
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "interface SimpleComponent {",
        "  SomeInjectableType someInjectableType();",
        "",
        "  @Component.Builder",
        "  static interface Builder {",
        "     SimpleComponent build();",
        "  }",
        "}");
    JavaFileObject generatedComponent = JavaFileObjects.forSourceLines(
        "test.DaggerSimpleComponent",
        "package test;",
        "",
        "import javax.annotation.Generated;",
        "import test.SimpleComponent",
        "",
        "@Generated(\"dagger.internal.codegen.ComponentProcessor\")",
        "public final class DaggerSimpleComponent implements SimpleComponent {",
        "  private DaggerSimpleComponent(Builder builder) {",
        "    assert builder != null;",
        "    initialize(builder);",
        "  }",
        "",
        "  public static SimpleComponent.Builder builder() {",
        "    return new Builder();",
        "  }",
        "",
        "  public static SimpleComponent create() {",
        "    return builder().build();",
        "  }",
        "",
        "  @SuppressWarnings(\"unchecked\")",
        "  private void initialize(final Builder builder) {",
        "  }",
        "",
        "  @Override",
        "  public SomeInjectableType someInjectableType() {",
        "    return SomeInjectableType_Factory.create().get();",
        "  }",
        "",
        "  private static final class Builder implements SimpleComponent.Builder {",
        "    @Override",
        "    public SimpleComponent build() {",
        "      return new DaggerSimpleComponent(this);",
        "    }",
        "  }",
        "}");
    assertAbout(javaSources()).that(ImmutableList.of(injectableTypeFile, componentFile))
        .processedWith(new ComponentProcessor())
        .compilesWithoutError()
        .and().generatesSources(generatedComponent);
  }

  @Test
  public void testUsesBuildAndSetterNames() {
    JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
        "package test;",
        "",
        "import dagger.Module;",
        "import dagger.Provides;",
        "",
        "@Module",
        "final class TestModule {",
        "  @Provides String string() { return null; }",
        "}");

    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "@Component(modules = TestModule.class)",
        "interface TestComponent {",
        "  String string();",
        "",
        "  @Component.Builder",
        "  interface Builder {",
        "    Builder setTestModule(TestModule testModule);",
        "    TestComponent create();",
        "  }",
        "}");
    JavaFileObject generatedComponent = JavaFileObjects.forSourceLines(
        "test.DaggerTestComponent",
        "package test;",
        "",
        "import javax.annotation.Generated;",
        "import javax.inject.Provider;",
        "import test.TestComponent;",
        "",
        "@Generated(\"dagger.internal.codegen.ComponentProcessor\")",
        "public final class DaggerTestComponent implements TestComponent {",
        "  private Provider<String> stringProvider;",
        "",
        "  private DaggerTestComponent(Builder builder) {",
        "    assert builder != null;",
        "    initialize(builder);",
        "  }",
        "",
        "  public static TestComponent.Builder builder() {",
        "    return new Builder();",
        "  }",
        "",
        "  public static TestComponent create() {",
        "    return builder().create();",
        "  }",
        "",
        "  @SuppressWarnings(\"unchecked\")",
        "  private void initialize(final Builder builder) {",
        "    this.stringProvider = TestModule_StringFactory.create(builder.testModule);",
        "  }",
        "",
        "  @Override",
        "  public String string() {",
        "    return stringProvider.get();",
        "  }",
        "",
        "  private static final class Builder implements TestComponent.Builder {",
        "    private TestModule testModule;",
        "",
        "    @Override",
        "    public TestComponent create() {",
        "      if (testModule == null) {",
        "        this.testModule = new TestModule();",
        "      }",
        "      return new DaggerTestComponent(this);",
        "    }",
        "",
        "    @Override",
        "    public Builder setTestModule(TestModule testModule) {",
        "      if (testModule == null) {",
        "        throw new NullPointerException();",
        "      }",
        "      this.testModule = testModule;",
        "      return this;",
        "    }",
        "  }",
        "}");
    assertAbout(javaSources())
        .that(ImmutableList.of(moduleFile, componentFile))
        .processedWith(new ComponentProcessor())
        .compilesWithoutError()
        .and().generatesSources(generatedComponent);
  }

  @Test
  public void testIgnoresModulesNotInApi() {
    JavaFileObject module1 = JavaFileObjects.forSourceLines("test.TestModule1",
        "package test;",
        "",
        "import dagger.Module;",
        "import dagger.Provides;",
        "",
        "@Module",
        "final class TestModule1 {",
        "  @Provides String string() { return null; }",
        "}");
    JavaFileObject module2 = JavaFileObjects.forSourceLines("test.TestModule2",
        "package test;",
        "",
        "import dagger.Module;",
        "import dagger.Provides;",
        "",
        "@Module",
        "final class TestModule2 {",
        "  @Provides Integer integer() { return null; }",
        "}");

    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "@Component(modules = {TestModule1.class, TestModule2.class})",
        "interface TestComponent {",
        "  String string();",
        "  Integer integer();",
        "",
        "  @Component.Builder",
        "  interface Builder {",
        "    Builder testModule1(TestModule1 testModule1);",
        "    TestComponent build();",
        "  }",
        "}");
    JavaFileObject generatedComponent = JavaFileObjects.forSourceLines(
        "test.DaggerTestComponent",
        "package test;",
        "",
        "import javax.annotation.Generated;",
        "import javax.inject.Provider;",
        "import test.TestComponent;",
        "",
        "@Generated(\"dagger.internal.codegen.ComponentProcessor\")",
        "public final class DaggerTestComponent implements TestComponent {",
        "  private Provider<String> stringProvider;",
        "  private Provider<Integer> integerProvider;",
        "",
        "  private DaggerTestComponent(Builder builder) {",
        "    assert builder != null;",
        "    initialize(builder);",
        "  }",
        "",
        "  public static TestComponent.Builder builder() {",
        "    return new Builder();",
        "  }",
        "",
        "  public static TestComponent create() {",
        "    return builder().build();",
        "  }",
        "",
        "  @SuppressWarnings(\"unchecked\")",
        "  private void initialize(final Builder builder) {",
        "    this.stringProvider = TestModule1_StringFactory.create(builder.testModule1);",
        "    this.integerProvider = TestModule2_IntegerFactory.create(builder.testModule2);",
        "  }",
        "",
        "  @Override",
        "  public String string() {",
        "    return stringProvider.get();",
        "  }",
        "",
        "  @Override",
        "  public Integer integer() {",
        "    return integerProvider.get();",
        "  }",
        "",
        "  private static final class Builder implements TestComponent.Builder {",
        "    private TestModule1 testModule1;",
        "    private TestModule2 testModule2;",
        "",
        "    @Override",
        "    public TestComponent build() {",
        "      if (testModule1 == null) {",
        "        this.testModule1 = new TestModule1();",
        "      }",
        "      if (testModule2 == null) {",
        "        this.testModule2 = new TestModule2();",
        "      }",
        "      return new DaggerTestComponent(this);",
        "    }",
        "",
        "    @Override",
        "    public Builder testModule1(TestModule1 testModule1) {",
        "      if (testModule1 == null) {",
        "        throw new NullPointerException();",
        "      }",
        "      this.testModule1 = testModule1;",
        "      return this;",
        "    }",
        "  }",
        "}");
    assertAbout(javaSources())
        .that(ImmutableList.of(module1, module2, componentFile))
        .processedWith(new ComponentProcessor())
        .compilesWithoutError()
        .and().generatesSources(generatedComponent);
  }

  @Test
  public void testMoreThanOneBuilderFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "interface SimpleComponent {",
        "  @Component.Builder",
        "  static interface Builder {",
        "     SimpleComponent build();",
        "  }",
        "",
        "  @Component.Builder",
        "  interface Builder2 {",
        "     SimpleComponent build();",
        "  }",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(String.format(MSGS.moreThanOne(),
            "[test.SimpleComponent.Builder, test.SimpleComponent.Builder2]"))
        .in(componentFile);
  }

  @Test
  public void testBuilderGenericsFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "interface SimpleComponent {",
        "  @Component.Builder",
        "  interface Builder<T> {",
        "     SimpleComponent build();",
        "  }",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(MSGS.generics())
        .in(componentFile);
  }

  @Test
  public void testBuilderNotInComponentFails() {
    JavaFileObject builder = JavaFileObjects.forSourceLines("test.Builder",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "@Component.Builder",
        "interface Builder {}");
    assertAbout(javaSource()).that(builder)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(MSGS.mustBeInComponent())
        .in(builder);
  }

  @Test
  public void testBuilderMissingBuildMethodFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "interface SimpleComponent {",
        "  @Component.Builder",
        "  interface Builder {}",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(MSGS.missingBuildMethod())
        .in(componentFile);
  }

  @Test
  public void testPrivateBuilderFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  @Component.Builder",
        "  private interface Builder {}",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(MSGS.isPrivate())
        .in(componentFile);
  }

  @Test
  public void testNonStaticBuilderFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  @Component.Builder",
        "  abstract class Builder {}",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(MSGS.mustBeStatic())
        .in(componentFile);
  }

  @Test
  public void testNonAbstractBuilderFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  @Component.Builder",
        "  static class Builder {}",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(MSGS.mustBeAbstract());
  }

  @Test
  public void testBuilderOneCxtorWithArgsFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  @Component.Builder",
        "  static abstract class Builder {",
        "    Builder(String unused) {}",
        "  }",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(MSGS.cxtorOnlyOneAndNoArgs())
        .in(componentFile);
  }

  @Test
  public void testBuilderMoreThanOneCxtorFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  @Component.Builder",
        "  static abstract class Builder {",
        "    Builder() {}",
        "    Builder(String unused) {}",
        "  }",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(MSGS.cxtorOnlyOneAndNoArgs())
        .in(componentFile);
  }

  @Test
  public void testBuilderEnumFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  @Component.Builder",
        "  enum Builder {}",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(MSGS.mustBeClassOrInterface())
        .in(componentFile);
  }

  @Test
  public void testBuilderBuildReturnsWrongTypeFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  @Component.Builder",
        "  interface Builder {",
        "    String build();",
        "  }",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(MSGS.buildMustReturnComponentType())
            .in(componentFile).onLine(11);
  }

  @Test
  public void testInheritedBuilderBuildReturnsWrongTypeFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  interface Parent {",
        "    String build();",
        "  }",
        "",
        "  @Component.Builder",
        "  interface Builder extends Parent {}",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(
            String.format(MSGS.inheritedBuildMustReturnComponentType(), "build"))
            .in(componentFile).onLine(14);
  }

  @Test
  public void testTwoBuildMethodsFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  @Component.Builder",
        "  interface Builder {",
        "    SimpleComponent build();",
        "    SimpleComponent create();",
        "  }",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(String.format(MSGS.twoBuildMethods(), "build()"))
            .in(componentFile).onLine(12);
  }

  @Test
  public void testInheritedTwoBuildMethodsFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  interface Parent {",
        "    SimpleComponent build();",
        "    SimpleComponent create();",
        "  }",
        "",
        "  @Component.Builder",
        "  interface Builder extends Parent {}",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(
            String.format(MSGS.inheritedTwoBuildMethods(), "create()", "build()"))
            .in(componentFile).onLine(15);
  }

  @Test
  public void testMoreThanOneArgFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  @Component.Builder",
        "  interface Builder {",
        "    SimpleComponent build();",
        "    Builder set(String s, Integer i);",
        "    Builder set(Number n, Double d);",
        "  }",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(MSGS.methodsMustTakeOneArg())
            .in(componentFile).onLine(12)
        .and().withErrorContaining(MSGS.methodsMustTakeOneArg())
            .in(componentFile).onLine(13);
  }

  @Test
  public void testInheritedMoreThanOneArgFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  interface Parent {",
        "    SimpleComponent build();",
        "    Builder set1(String s, Integer i);",
        "  }",
        "",
        "  @Component.Builder",
        "  interface Builder extends Parent {}",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(
            String.format(MSGS.inheritedMethodsMustTakeOneArg(),
                "set1(java.lang.String,java.lang.Integer)"))
            .in(componentFile).onLine(15);
  }

  @Test
  public void testSetterReturningNonVoidOrBuilderFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  @Component.Builder",
        "  interface Builder {",
        "    SimpleComponent build();",
        "    String set(Integer i);",
        "  }",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(MSGS.methodsMustReturnVoidOrBuilder())
            .in(componentFile).onLine(12);
  }

  @Test
  public void testInheritedSetterReturningNonVoidOrBuilderFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  interface Parent {",
        "    SimpleComponent build();",
        "    String set(Integer i);",
        "  }",
        "",
        "  @Component.Builder",
        "  interface Builder extends Parent {}",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(
            String.format(MSGS.inheritedMethodsMustReturnVoidOrBuilder(),
                "set(java.lang.Integer)"))
            .in(componentFile).onLine(15);
  }

  @Test
  public void testGenericsOnSetterMethodFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  @Component.Builder",
        "  interface Builder {",
        "    SimpleComponent build();",
        "    <T> Builder set(T t);",
        "  }",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(MSGS.methodsMayNotHaveTypeParameters())
            .in(componentFile).onLine(12);
  }

  @Test
  public void testGenericsOnInheritedSetterMethodFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  interface Parent {",
        "    SimpleComponent build();",
        "    <T> Builder set(T t);",
        "  }",
        "",
        "  @Component.Builder",
        "  interface Builder extends Parent {}",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(
            String.format(MSGS.inheritedMethodsMayNotHaveTypeParameters(), "<T>set(T)"))
            .in(componentFile).onLine(15);
  }

  @Test
  public void testMultipleSettersPerTypeFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  @Component.Builder",
        "  interface Builder {",
        "    SimpleComponent build();",
        "    void set1(String s);",
        "    void set2(String s);",
        "  }",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(
            String.format(MSGS.manyMethodsForType(),
                  "java.lang.String", "[set1(java.lang.String), set2(java.lang.String)]"))
            .in(componentFile).onLine(10);
  }

  @Test
  public void testMultipleSettersPerTypeIncludingResolvedGenericsFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  interface Parent<T> {",
        "    void set1(T t);",
        "  }",
        "",
        "  @Component.Builder",
        "  interface Builder extends Parent<String> {",
        "    SimpleComponent build();",
        "    void set2(String s);",
        "  }",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(
            String.format(MSGS.manyMethodsForType(),
                  "java.lang.String", "[set1(T), set2(java.lang.String)]"))
            .in(componentFile).onLine(14);
  }

  @Test
  public void testExtraSettersFails() {
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "abstract class SimpleComponent {",
        "  @Component.Builder",
        "  interface Builder {",
        "    SimpleComponent build();",
        "    void set1(String s);",
        "    void set2(Integer s);",
        "  }",
        "}");
    assertAbout(javaSource()).that(componentFile)
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(
            String.format(MSGS.extraSetters(),
                  "[void test.SimpleComponent.Builder.set1(String),"
                  + " void test.SimpleComponent.Builder.set2(Integer)]"))
            .in(componentFile).onLine(10);

  }

  @Test
  public void testMissingSettersFail() {
    JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
        "package test;",
        "",
        "import dagger.Module;",
        "import dagger.Provides;",
        "",
        "@Module",
        "final class TestModule {",
        "  TestModule(String unused) {}",
        "  @Provides String s() { return null; }",
        "}");
    JavaFileObject module2File = JavaFileObjects.forSourceLines("test.Test2Module",
        "package test;",
        "",
        "import dagger.Module;",
        "import dagger.Provides;",
        "",
        "@Module",
        "final class Test2Module {",
        "  @Provides Integer i() { return null; }",
        "}");
    JavaFileObject module3File = JavaFileObjects.forSourceLines("test.Test3Module",
        "package test;",
        "",
        "import dagger.Module;",
        "import dagger.Provides;",
        "",
        "@Module",
        "final class Test3Module {",
        "  Test3Module(String unused) {}",
        "  @Provides Double d() { return null; }",
        "}");
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "@Component(modules = {TestModule.class, Test2Module.class, Test3Module.class},",
        "           dependencies = OtherComponent.class)",
        "interface TestComponent {",
        "  String string();",
        "  Integer integer();",
        "",
        "  @Component.Builder",
        "  interface Builder {",
        "    TestComponent create();",
        "  }",
        "}");
    JavaFileObject otherComponent = JavaFileObjects.forSourceLines("test.OtherComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "@Component",
        "interface OtherComponent {}");
    assertAbout(javaSources())
        .that(ImmutableList.of(moduleFile, module2File, module3File, componentFile, otherComponent))
        .processedWith(new ComponentProcessor())
        .failsToCompile()
        .withErrorContaining(
            // Ignores Test2Module because we can construct it ourselves.
            // TODO(sameb): Ignore Test3Module because it's not used within transitive dependencies.
            String.format(MSGS.missingSetters(),
                "[test.TestModule, test.Test3Module, test.OtherComponent]"))
            .in(componentFile).onLine(12);
  }
}