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

import static com.google.common.truth.Truth.assertAbout;
import static com.google.testing.compile.CompilationSubject.assertThat;
import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE;
import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE;
import static dagger.internal.codegen.Compilers.compilerWithOptions;
import static javax.tools.StandardLocation.CLASS_OUTPUT;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import java.io.IOException;
import java.io.Writer;
import java.util.Collection;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class MembersInjectionTest {
  @Parameters(name = "{0}")
  public static Collection<Object[]> parameters() {
    return CompilerMode.TEST_PARAMETERS;
  }

  private final CompilerMode compilerMode;

  public MembersInjectionTest(CompilerMode compilerMode) {
    this.compilerMode = compilerMode;
  }

  @Test
  public void parentClass_noInjectedMembers() {
    JavaFileObject childFile = JavaFileObjects.forSourceLines("test.Child",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "public final class Child extends Parent {",
        "  @Inject Child() {}",
        "}");
    JavaFileObject parentFile = JavaFileObjects.forSourceLines("test.Parent",
        "package test;",
        "",
        "public abstract class Parent {}");

    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "@Component",
        "interface TestComponent {",
        "  Child child();",
        "}");
    JavaFileObject generatedComponent =
        JavaFileObjects.forSourceLines(
            "test.DaggerTestComponent",
            "package test;",
            "",
            GeneratedLines.generatedAnnotations(),
            "final class DaggerTestComponent implements TestComponent {",
            "  @Override",
            "  public Child child() {",
            "    return new Child();",
            "  }",
            "}");
    Compilation compilation =
        compilerWithOptions(compilerMode.javacopts())
            .compile(childFile, parentFile, componentFile);

    assertThat(compilation).succeeded();
    assertThat(compilation)
        .generatedSourceFile("test.DaggerTestComponent")
        .containsElementsIn(generatedComponent);
  }

  @Test
  public void parentClass_injectedMembersInSupertype() {
    JavaFileObject childFile = JavaFileObjects.forSourceLines("test.Child",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "public final class Child extends Parent {",
        "  @Inject Child() {}",
        "}");
    JavaFileObject parentFile = JavaFileObjects.forSourceLines("test.Parent",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "public abstract class Parent {",
        "  @Inject Dep dep;",
        "}");
    JavaFileObject depFile = JavaFileObjects.forSourceLines("test.Dep",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "final class Dep {",
        "  @Inject Dep() {}",
        "}");
    JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "",
        "@Component",
        "interface TestComponent {",
        "  Child child();",
        "}");
    JavaFileObject generatedComponent =
        JavaFileObjects.forSourceLines(
            "test.DaggerTestComponent",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import com.google.errorprone.annotations.CanIgnoreReturnValue;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "final class DaggerTestComponent implements TestComponent {",
            "  @Override",
            "  public Child child() {",
            "    return injectChild(Child_Factory.newInstance());",
            "  }",
            "",
            "  @CanIgnoreReturnValue",
            "  private Child injectChild(Child instance) {",
            "    Parent_MembersInjector.injectDep(instance, new Dep());",
            "    return instance;",
            "  }",
            "}");
    Compilation compilation =
        compilerWithOptions(compilerMode.javacopts())
            .compile(childFile, parentFile, depFile, componentFile);

    assertThat(compilation).succeeded();
    assertThat(compilation)
        .generatedSourceFile("test.DaggerTestComponent")
        .containsElementsIn(generatedComponent);
  }

  @Test public void fieldAndMethodGenerics() {
    JavaFileObject file = JavaFileObjects.forSourceLines("test.GenericClass",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "class GenericClass<A, B> {",
        "  @Inject A a;",
        "",
        "  @Inject GenericClass() {}",
        "",
        " @Inject void register(B b) {}",
        "}");
    JavaFileObject expected =
        JavaFileObjects.forSourceLines(
            "test.GenericClass_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class GenericClass_MembersInjector<A, B>",
            "    implements MembersInjector<GenericClass<A, B>> {",
            "  private final Provider<A> aProvider;",
            "  private final Provider<B> bProvider;",
            "",
            "  public GenericClass_MembersInjector(Provider<A> aProvider, Provider<B> bProvider) {",
            "    this.aProvider = aProvider;",
            "    this.bProvider = bProvider;",
            "  }",
            "",
            "  public static <A, B> MembersInjector<GenericClass<A, B>> create(",
            "      Provider<A> aProvider, Provider<B> bProvider) {",
            "    return new GenericClass_MembersInjector<A, B>(aProvider, bProvider);",
            "  }",
            "",
            "  @Override",
            "  public void injectMembers(GenericClass<A, B> instance) {",
            "    injectA(instance, aProvider.get());",
            "    injectRegister(instance, bProvider.get());",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.GenericClass.a\")",
            "  public static <A, B> void injectA(Object instance, A a) {",
            "    ((GenericClass<A, B>) instance).a = a;",
            "  }",
            "",
            "  public static <A, B> void injectRegister(Object instance, B b) {",
            "    ((GenericClass<A, B>) instance).register(b);",
            "  }",
            "}");
    assertAbout(javaSource())
        .that(file)
        .withCompilerOptions(compilerMode.javacopts())
        .processedWith(new ComponentProcessor())
        .compilesWithoutError()
        .and()
        .generatesSources(expected);
  }

  @Test public void subclassedGenericMembersInjectors() {
    JavaFileObject a = JavaFileObjects.forSourceLines("test.A",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "final class A {",
        "  @Inject A() {}",
        "}");
    JavaFileObject a2 = JavaFileObjects.forSourceLines("test.A2",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "final class A2 {",
        "  @Inject A2() {}",
        "}");
    JavaFileObject parent = JavaFileObjects.forSourceLines("test.Parent",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "class Parent<X, Y> {",
        "  @Inject X x;",
        "  @Inject Y y;",
        "  @Inject A2 a2;",
        "",
        "  @Inject Parent() {}",
        "}");
    JavaFileObject child = JavaFileObjects.forSourceLines("test.Child",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "class Child<T> extends Parent<T, A> {",
        "  @Inject A a;",
        "  @Inject T t;",
        "",
        "  @Inject Child() {}",
        "}");
    JavaFileObject expected =
        JavaFileObjects.forSourceLines(
            "test.Child_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class Child_MembersInjector<T>",
            "    implements MembersInjector<Child<T>> {",
            "  private final Provider<T> xProvider;",
            "  private final Provider<A> yProvider;",
            "  private final Provider<A2> a2Provider;",
            "  private final Provider<A> aProvider;",
            "  private final Provider<T> tProvider;",
            "",
            "  public Child_MembersInjector(",
            "      Provider<T> xProvider,",
            "      Provider<A> yProvider,",
            "      Provider<A2> a2Provider,",
            "      Provider<A> aProvider,",
            "      Provider<T> tProvider) {",
            "    this.xProvider = xProvider;",
            "    this.yProvider = yProvider;",
            "    this.a2Provider = a2Provider;",
            "    this.aProvider = aProvider;",
            "    this.tProvider = tProvider;",
            "  }",
            "",
            "  public static <T> MembersInjector<Child<T>> create(",
            "      Provider<T> xProvider,",
            "      Provider<A> yProvider,",
            "      Provider<A2> a2Provider,",
            "      Provider<A> aProvider,",
            "      Provider<T> tProvider) {",
            "    return new Child_MembersInjector<T>(xProvider, yProvider, a2Provider, aProvider,"
                + " tProvider);",
            "}",
            "",
            "  @Override",
            "  public void injectMembers(Child<T> instance) {",
            "    Parent_MembersInjector.injectX(instance, xProvider.get());",
            "    Parent_MembersInjector.injectY(instance, yProvider.get());",
            "    Parent_MembersInjector.injectA2(instance, a2Provider.get());",
            "    injectA(instance, aProvider.get());",
            "    injectT(instance, tProvider.get());",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.Child.a\")",
            "  public static <T> void injectA(Object instance, Object a) {",
            "    ((Child<T>) instance).a = (A) a;",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.Child.t\")",
            "  public static <T> void injectT(Object instance, T t) {",
            "    ((Child<T>) instance).t = t;",
            "  }",
            "}");
    assertAbout(javaSources())
        .that(ImmutableList.of(a, a2, parent, child))
        .withCompilerOptions(compilerMode.javacopts())
        .processedWith(new ComponentProcessor())
        .compilesWithoutError()
        .and()
        .generatesSources(expected);
  }

  @Test public void fieldInjection() {
    JavaFileObject file = JavaFileObjects.forSourceLines("test.FieldInjection",
        "package test;",
        "",
        "import dagger.Lazy;",
        "import javax.inject.Inject;",
        "import javax.inject.Provider;",
        "",
        "class FieldInjection {",
        "  @Inject String string;",
        "  @Inject Lazy<String> lazyString;",
        "  @Inject Provider<String> stringProvider;",
        "}");
    JavaFileObject expected =
        JavaFileObjects.forSourceLines(
            "test.FieldInjection_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.Lazy;",
                "import dagger.MembersInjector;",
                "import dagger.internal.DoubleCheck;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class FieldInjection_MembersInjector",
            "    implements MembersInjector<FieldInjection> {",
            "  private final Provider<String> stringProvider;",
            "  private final Provider<String> stringProvider2;",
            "  private final Provider<String> stringProvider3;",
            "",
            "  public FieldInjection_MembersInjector(Provider<String> stringProvider,",
            "      Provider<String> stringProvider2, Provider<String> stringProvider3) {",
            "    this.stringProvider = stringProvider;",
            "    this.stringProvider2 = stringProvider2;",
            "    this.stringProvider3 = stringProvider3;",
            "  }",
            "",
            "  public static MembersInjector<FieldInjection> create(",
            "      Provider<String> stringProvider,",
            "      Provider<String> stringProvider2,",
            "      Provider<String> stringProvider3) {",
            "    return new FieldInjection_MembersInjector(",
            "        stringProvider, stringProvider2, stringProvider3);",
            "  }",
            "",
            "  @Override",
            "  public void injectMembers(FieldInjection instance) {",
            "    injectString(instance, stringProvider.get());",
            "    injectLazyString(instance, DoubleCheck.lazy(stringProvider2));",
            "    injectStringProvider(instance, stringProvider3);",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.FieldInjection.string\")",
            "  public static void injectString(Object instance, String string) {",
            "    ((FieldInjection) instance).string = string;",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.FieldInjection.lazyString\")",
            "  public static void injectLazyString(Object instance, Lazy<String> lazyString) {",
            "    ((FieldInjection) instance).lazyString = lazyString;",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.FieldInjection.stringProvider\")",
            "  public static void injectStringProvider(",
            "      Object instance, Provider<String> stringProvider) {",
            "    ((FieldInjection) instance).stringProvider = stringProvider;",
            "  }",
            "}");
    assertAbout(javaSource())
        .that(file)
        .withCompilerOptions(compilerMode.javacopts())
        .processedWith(new ComponentProcessor())
        .compilesWithoutError()
        .and()
        .generatesSources(expected);
  }

  @Test
  public void fieldInjectionWithQualifier() {
    JavaFileObject file =
        JavaFileObjects.forSourceLines(
            "test.FieldInjectionWithQualifier",
            "package test;",
            "",
            "import dagger.Lazy;",
            "import javax.inject.Inject;",
            "import javax.inject.Named;",
            "import javax.inject.Provider;",
            "",
            "class FieldInjectionWithQualifier {",
            "  @Inject @Named(\"A\") String a;",
            "  @Inject @Named(\"B\") String b;",
            "}");
    JavaFileObject expected =
        JavaFileObjects.forSourceLines(
            "test.FieldInjectionWithQualifier_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Named;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class FieldInjectionWithQualifier_MembersInjector",
            "    implements MembersInjector<FieldInjectionWithQualifier> {",
            "  private final Provider<String> aProvider;",
            "  private final Provider<String> bProvider;",
            "",
            "  public FieldInjectionWithQualifier_MembersInjector(Provider<String> aProvider,",
            "      Provider<String> bProvider) {",
            "    this.aProvider = aProvider;",
            "    this.bProvider = bProvider;",
            "  }",
            "",
            "  public static MembersInjector<FieldInjectionWithQualifier> create(",
            "    Provider<String> aProvider, Provider<String> bProvider) {",
            "    return new FieldInjectionWithQualifier_MembersInjector(aProvider, bProvider);",
            "  }",
            "",
            "@Override",
            "  public void injectMembers(FieldInjectionWithQualifier instance) {",
            "    injectA(instance, aProvider.get());",
            "    injectB(instance, bProvider.get());",
            "}",
            "",
            "  @InjectedFieldSignature(\"test.FieldInjectionWithQualifier.a\")",
            "  @Named(\"A\")",
            "  public static void injectA(Object instance, String a) {",
            "    ((FieldInjectionWithQualifier) instance).a = a;",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.FieldInjectionWithQualifier.b\")",
            "  @Named(\"B\")",
            "  public static void injectB(Object instance, String b) {",
            "    ((FieldInjectionWithQualifier) instance).b = b;",
            "  }",
            "}");
    assertAbout(javaSource())
        .that(file)
        .withCompilerOptions(compilerMode.javacopts())
        .processedWith(new ComponentProcessor())
        .compilesWithoutError()
        .and()
        .generatesSources(expected);
  }

  @Test public void methodInjection() {
    JavaFileObject file = JavaFileObjects.forSourceLines("test.MethodInjection",
        "package test;",
        "",
        "import dagger.Lazy;",
        "import javax.inject.Inject;",
        "import javax.inject.Provider;",
        "",
        "class MethodInjection {",
        "  @Inject void noArgs() {}",
        "  @Inject void oneArg(String string) {}",
        "  @Inject void manyArgs(",
        "      String string, Lazy<String> lazyString, Provider<String> stringProvider) {}",
        "}");
    JavaFileObject expected =
        JavaFileObjects.forSourceLines(
            "test.MethodInjection_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.Lazy;",
                "import dagger.MembersInjector;",
                "import dagger.internal.DoubleCheck;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class MethodInjection_MembersInjector",
            "     implements MembersInjector<MethodInjection> {",
            "  private final Provider<String> stringProvider;",
            "  private final Provider<String> stringProvider2;",
            "  private final Provider<String> stringProvider3;",
            "  private final Provider<String> stringProvider4;",
            "",
            "  public MethodInjection_MembersInjector(",
            "      Provider<String> stringProvider,",
            "      Provider<String> stringProvider2,",
            "      Provider<String> stringProvider3,",
            "      Provider<String> stringProvider4) {",
            "    this.stringProvider = stringProvider;",
            "    this.stringProvider2 = stringProvider2;",
            "    this.stringProvider3 = stringProvider3;",
            "    this.stringProvider4 = stringProvider4;",
            "  }",
            "",
            "  public static MembersInjector<MethodInjection> create(",
            "      Provider<String> stringProvider,",
            "      Provider<String> stringProvider2,",
            "      Provider<String> stringProvider3,",
            "      Provider<String> stringProvider4) {",
            "    return new MethodInjection_MembersInjector(",
            "        stringProvider, stringProvider2, stringProvider3, stringProvider4);}",
            "",
            "  @Override",
            "  public void injectMembers(MethodInjection instance) {",
            "    injectNoArgs(instance);",
            "    injectOneArg(instance, stringProvider.get());",
            "    injectManyArgs(",
            "        instance,",
            "        stringProvider2.get(),",
            "        DoubleCheck.lazy(stringProvider3),",
            "        stringProvider4);",
            "  }",
            "",
            "  public static void injectNoArgs(Object instance) {",
            "    ((MethodInjection) instance).noArgs();",
            "  }",
            "",
            "  public static void injectOneArg(Object instance, String string) {",
            "    ((MethodInjection) instance).oneArg(string);",
            "  }",
            "",
            "  public static void injectManyArgs(",
            "      Object instance,",
            "      String string,",
            "      Lazy<String> lazyString,",
            "      Provider<String> stringProvider) {",
            "    ((MethodInjection) instance).manyArgs(string, lazyString, stringProvider);",
            "  }",
            "}");
    assertAbout(javaSource())
        .that(file)
        .withCompilerOptions(compilerMode.javacopts())
        .processedWith(new ComponentProcessor())
        .compilesWithoutError()
        .and()
        .generatesSources(expected);
  }

  @Test
  public void mixedMemberInjection() {
    JavaFileObject file = JavaFileObjects.forSourceLines(
        "test.MixedMemberInjection",
        "package test;",
        "",
        "import dagger.Lazy;",
        "import javax.inject.Inject;",
        "import javax.inject.Provider;",
        "",
        "class MixedMemberInjection {",
        "  @Inject String string;",
        "  @Inject void setString(String s) {}",
        "  @Inject Object object;",
        "  @Inject void setObject(Object o) {}",
        "}");
    JavaFileObject expected =
        JavaFileObjects.forSourceLines(
            "test.MixedMemberInjection_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class MixedMemberInjection_MembersInjector",
            "    implements MembersInjector<MixedMemberInjection> {",
            "  private final Provider<String> stringProvider;",
            "  private final Provider<Object> objectProvider;",
            "  private final Provider<String> sProvider;",
            "  private final Provider<Object> oProvider;",
            "",
            "  public MixedMemberInjection_MembersInjector(",
            "      Provider<String> stringProvider,",
            "      Provider<Object> objectProvider,",
            "      Provider<String> sProvider,",
            "      Provider<Object> oProvider) {",
            "    this.stringProvider = stringProvider;",
            "    this.objectProvider = objectProvider;",
            "    this.sProvider = sProvider;",
            "    this.oProvider = oProvider;",
            "  }",
            "",
            "  public static MembersInjector<MixedMemberInjection> create(",
            "      Provider<String> stringProvider,",
            "      Provider<Object> objectProvider,",
            "      Provider<String> sProvider,",
            "      Provider<Object> oProvider) {",
            "    return new MixedMemberInjection_MembersInjector(",
            "        stringProvider, objectProvider, sProvider, oProvider);}",
            "",
            "  @Override",
            "  public void injectMembers(MixedMemberInjection instance) {",
            "    injectString(instance, stringProvider.get());",
            "    injectObject(instance, objectProvider.get());",
            "    injectSetString(instance, sProvider.get());",
            "    injectSetObject(instance, oProvider.get());",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.MixedMemberInjection.string\")",
            "  public static void injectString(Object instance, String string) {",
            "    ((MixedMemberInjection) instance).string = string;",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.MixedMemberInjection.object\")",
            "  public static void injectObject(Object instance, Object object) {",
            "    ((MixedMemberInjection) instance).object = object;",
            "  }",
            "",
            "  public static void injectSetString(Object instance, String s) {",
            "    ((MixedMemberInjection) instance).setString(s);",
            "  }",
            "",
            "  public static void injectSetObject(Object instance, Object o) {",
            "    ((MixedMemberInjection) instance).setObject(o);",
            "  }",
            "}");
    assertAbout(javaSource())
        .that(file)
        .withCompilerOptions(compilerMode.javacopts())
        .processedWith(new ComponentProcessor())
        .compilesWithoutError()
        .and()
        .generatesSources(expected);
  }

  @Test public void injectConstructorAndMembersInjection() {
    JavaFileObject file = JavaFileObjects.forSourceLines("test.AllInjections",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "class AllInjections {",
        "  @Inject String s;",
        "  @Inject AllInjections(String s) {}",
        "  @Inject void s(String s) {}",
        "}");
    JavaFileObject expectedMembersInjector =
        JavaFileObjects.forSourceLines(
            "test.AllInjections_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class AllInjections_MembersInjector ",
            "    implements MembersInjector<AllInjections> {",
            "  private final Provider<String> sProvider;",
            "  private final Provider<String> sProvider2;",
            "",
            "  public AllInjections_MembersInjector(",
            "      Provider<String> sProvider, Provider<String> sProvider2) {",
            "    this.sProvider = sProvider;",
            "    this.sProvider2 = sProvider2;",
            "  }",
            "",
            "  public static MembersInjector<AllInjections> create(",
            "      Provider<String> sProvider, Provider<String> sProvider2) {",
            "    return new AllInjections_MembersInjector(sProvider, sProvider2);}",
            "",
            "  @Override",
            "  public void injectMembers(AllInjections instance) {",
            "    injectS(instance, sProvider.get());",
            "    injectS2(instance, sProvider2.get());",
            "  }",
            "",
            // TODO(b/64477506): now that these all take "object", it would be nice to rename
            // "instance"
            // to the type name
            "  @InjectedFieldSignature(\"test.AllInjections.s\")",
            "  public static void injectS(Object instance, String s) {",
            "    ((AllInjections) instance).s = s;",
            "  }",
            "",
            "  public static void injectS2(Object instance, String s) {",
            "    ((AllInjections) instance).s(s);",
            "  }",
            "",
            "}");
    assertAbout(javaSource())
        .that(file)
        .withCompilerOptions(compilerMode.javacopts())
        .processedWith(new ComponentProcessor())
        .compilesWithoutError()
        .and()
        .generatesSources(expectedMembersInjector);
  }

  @Test public void supertypeMembersInjection() {
    JavaFileObject aFile = JavaFileObjects.forSourceLines("test.A",
        "package test;",
        "",
        "class A {}");
    JavaFileObject bFile = JavaFileObjects.forSourceLines("test.B",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "class B extends A {",
        "  @Inject String s;",
        "}");
    JavaFileObject expectedMembersInjector =
        JavaFileObjects.forSourceLines(
            "test.AllInjections_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class B_MembersInjector implements MembersInjector<B> {",
            "  private final Provider<String> sProvider;",
            "",
            "  public B_MembersInjector(Provider<String> sProvider) {",
            "    this.sProvider = sProvider;",
            "  }",
            "",
            "  public static MembersInjector<B> create(Provider<String> sProvider) {",
            "      return new B_MembersInjector(sProvider);",
            "  }",
            "",
            "  @Override",
            "  public void injectMembers(B instance) {",
            "    injectS(instance, sProvider.get());",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.B.s\")",
            "  public static void injectS(Object instance, String s) {",
            "    ((B) instance).s = s;",
            "  }",
            "}");
    assertAbout(javaSources())
        .that(ImmutableList.of(aFile, bFile))
        .withCompilerOptions(compilerMode.javacopts())
        .processedWith(new ComponentProcessor())
        .compilesWithoutError()
        .and()
        .generatesSources(expectedMembersInjector);
  }

  @Test
  public void simpleComponentWithNesting() {
    JavaFileObject nestedTypesFile = JavaFileObjects.forSourceLines(
          "test.OuterType",
          "package test;",
          "",
          "import dagger.Component;",
          "import javax.inject.Inject;",
          "",
          "final class OuterType {",
          "  static class A {",
          "    @Inject A() {}",
          "  }",
          "  static class B {",
          "    @Inject A a;",
          "  }",
          "  @Component interface SimpleComponent {",
          "    A a();",
          "    void inject(B b);",
          "  }",
          "}");
    JavaFileObject bMembersInjector =
        JavaFileObjects.forSourceLines(
            "test.OuterType_B_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class OuterType_B_MembersInjector",
            "    implements MembersInjector<OuterType.B> {",
            "  private final Provider<OuterType.A> aProvider;",
            "",
            "  public OuterType_B_MembersInjector(Provider<OuterType.A> aProvider) {",
            "    this.aProvider = aProvider;",
            "  }",
            "",
            "  public static MembersInjector<OuterType.B> create(",
            "    Provider<OuterType.A> aProvider) {",
            "    return new OuterType_B_MembersInjector(aProvider);",
            "  }",
            "",
            "  @Override",
            "  public void injectMembers(OuterType.B instance) {",
            "    injectA(instance, aProvider.get());",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.OuterType.B.a\")",
            "  public static void injectA(Object instance, Object a) {",
            "    ((OuterType.B) instance).a = (OuterType.A) a;",
            "  }",
            "}");
    assertAbout(javaSources())
        .that(ImmutableList.of(nestedTypesFile))
        .withCompilerOptions(compilerMode.javacopts())
        .processedWith(new ComponentProcessor())
        .compilesWithoutError()
        .and()
        .generatesSources(bMembersInjector);
  }

  @Test
  public void componentWithNestingAndGeneratedType() {
    JavaFileObject nestedTypesFile =
        JavaFileObjects.forSourceLines(
            "test.OuterType",
            "package test;",
            "",
            "import dagger.Component;",
            "import javax.inject.Inject;",
            "",
            "final class OuterType {",
            "  @Inject GeneratedType generated;",
            "  static class A {",
            "    @Inject A() {}",
            "  }",
            "  static class B {",
            "    @Inject A a;",
            "  }",
            "  @Component interface SimpleComponent {",
            "    A a();",
            "    void inject(B b);",
            "  }",
            "}");
    JavaFileObject bMembersInjector =
        JavaFileObjects.forSourceLines(
            "test.OuterType_B_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class OuterType_B_MembersInjector",
            "    implements MembersInjector<OuterType.B> {",
            "  private final Provider<OuterType.A> aProvider;",
            "",
            "  public OuterType_B_MembersInjector(Provider<OuterType.A> aProvider) {",
            "    this.aProvider = aProvider;",
            "  }",
            "",
            "  public static MembersInjector<OuterType.B> create(",
            "      Provider<OuterType.A> aProvider) {",
            "    return new OuterType_B_MembersInjector(aProvider);",
            "  }",
            "",
            "  @Override",
            "  public void injectMembers(OuterType.B instance) {",
            "    injectA(instance, aProvider.get());",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.OuterType.B.a\")",
            "  public static void injectA(Object instance, Object a) {",
            "    ((OuterType.B) instance).a = (OuterType.A) a;",
            "  }",
            "}");
    assertAbout(javaSource())
        .that(nestedTypesFile)
        .withCompilerOptions(compilerMode.javacopts())
        .processedWith(
            new ComponentProcessor(),
            new AbstractProcessor() {
              private boolean done;

              @Override
              public Set<String> getSupportedAnnotationTypes() {
                return ImmutableSet.of("*");
              }

              @Override
              public boolean process(
                  Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
                if (!done) {
                  done = true;
                  try (Writer writer =
                      processingEnv
                          .getFiler()
                          .createSourceFile("test.GeneratedType")
                          .openWriter()) {
                    writer.write(
                        Joiner.on('\n')
                            .join(
                                "package test;",
                                "",
                                "import javax.inject.Inject;",
                                "",
                                "class GeneratedType {",
                                "  @Inject GeneratedType() {}",
                                "}"));
                  } catch (IOException e) {
                    throw new RuntimeException(e);
                  }
                }
                return false;
              }
            })
        .compilesWithoutError()
        .and()
        .generatesSources(bMembersInjector);
  }

  @Test
  public void lowerCaseNamedMembersInjector_forLowerCaseType() {
    JavaFileObject foo =
        JavaFileObjects.forSourceLines(
            "test.foo",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class foo {",
            "  @Inject String string;",
            "}");
    JavaFileObject fooModule =
        JavaFileObjects.forSourceLines(
            "test.fooModule",
            "package test;",
            "",
            "import dagger.Module;",
            "import dagger.Provides;",
            "",
            "@Module",
            "class fooModule {",
            "  @Provides String string() { return \"foo\"; }",
            "}");
    JavaFileObject fooComponent =
        JavaFileObjects.forSourceLines(
            "test.fooComponent",
            "package test;",
            "",
            "import dagger.Component;",
            "",
            "@Component(modules = fooModule.class)",
            "interface fooComponent {",
            "  void inject(foo target);",
            "}");

    Compilation compilation =
        compilerWithOptions(compilerMode.javacopts())
            .compile(foo, fooModule, fooComponent);
    assertThat(compilation).succeeded();
    assertThat(compilation).generatedFile(CLASS_OUTPUT, "test", "foo_MembersInjector.class");
  }

  @Test
  public void fieldInjectionForShadowedMember() {
    JavaFileObject foo =
        JavaFileObjects.forSourceLines(
            "test.Foo",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class Foo {",
            "  @Inject Foo() {}",
            "}");
    JavaFileObject bar =
        JavaFileObjects.forSourceLines(
            "test.Bar",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class Bar {",
            "  @Inject Bar() {}",
            "}");
    JavaFileObject parent =
        JavaFileObjects.forSourceLines(
            "test.Parent",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class Parent { ",
            "  @Inject Foo object;",
            "}");
    JavaFileObject child =
        JavaFileObjects.forSourceLines(
            "test.Child",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class Child extends Parent { ",
            "  @Inject Bar object;",
            "}");
    JavaFileObject component =
        JavaFileObjects.forSourceLines(
            "test.C",
            "package test;",
            "",
            "import dagger.Component;",
            "",
            "@Component",
            "interface C { ",
            "  void inject(Child child);",
            "}");

    JavaFileObject expectedMembersInjector =
        JavaFileObjects.forSourceLines(
            "test.Child_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class Child_MembersInjector implements MembersInjector<Child> {",
            "  private final Provider<Foo> objectProvider;",
            "  private final Provider<Bar> objectProvider2;",
            "",
            "  public Child_MembersInjector(",
            "        Provider<Foo> objectProvider, Provider<Bar> objectProvider2) {",
            "    this.objectProvider = objectProvider;",
            "    this.objectProvider2 = objectProvider2;",
            "  }",
            "",
            "  public static MembersInjector<Child> create(",
            "      Provider<Foo> objectProvider, Provider<Bar> objectProvider2) {",
            "    return new Child_MembersInjector(objectProvider, objectProvider2);",
            "  }",
            "",
            "  @Override",
            "  public void injectMembers(Child instance) {",
            "    Parent_MembersInjector.injectObject(instance, objectProvider.get());",
            "    injectObject(instance, objectProvider2.get());",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.Child.object\")",
            "  public static void injectObject(Object instance, Object object) {",
            "    ((Child) instance).object = (Bar) object;",
            "  }",
            "}");

    assertAbout(javaSources())
        .that(ImmutableList.of(foo, bar, parent, child, component))
        .withCompilerOptions(compilerMode.javacopts())
        .processedWith(new ComponentProcessor())
        .compilesWithoutError()
        .and()
        .generatesSources(expectedMembersInjector);
  }

  @Test public void privateNestedClassError() {
    JavaFileObject file = JavaFileObjects.forSourceLines("test.OuterClass",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "final class OuterClass {",
        "  private static final class InnerClass {",
        "    @Inject int field;",
        "  }",
        "}");
    Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(file);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining("Dagger does not support injection into private classes")
        .inFile(file)
        .onLine(6);
  }

  @Test public void privateNestedClassWarning() {
    JavaFileObject file = JavaFileObjects.forSourceLines("test.OuterClass",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "final class OuterClass {",
        "  private static final class InnerClass {",
        "    @Inject int field;",
        "  }",
        "}");
    Compilation compilation =
        compilerWithOptions(
                compilerMode.javacopts().append("-Adagger.privateMemberValidation=WARNING"))
            .compile(file);
    assertThat(compilation).succeeded();
    assertThat(compilation)
        .hadWarningContaining("Dagger does not support injection into private classes")
        .inFile(file)
        .onLine(6);
  }

  @Test public void privateSuperclassIsOkIfNotInjectedInto() {
    JavaFileObject file = JavaFileObjects.forSourceLines("test.OuterClass",
        "package test;",
        "",
        "import javax.inject.Inject;",
        "",
        "final class OuterClass {",
        "  private static class BaseClass {}",
        "",
        "  static final class DerivedClass extends BaseClass {",
        "    @Inject int field;",
        "  }",
        "}");
    Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(file);
    assertThat(compilation).succeeded();
  }

  @Test
  public void rawFrameworkTypeField() {
    JavaFileObject file =
        JavaFileObjects.forSourceLines(
            "test.RawFrameworkTypes",
            "package test;",
            "",
            "import dagger.Component;",
            "import javax.inject.Inject;",
            "import javax.inject.Provider;",
            "",
            "class RawProviderField {",
            "  @Inject Provider fieldWithRawProvider;",
            "}",
            "",
            "@Component",
            "interface C {",
            "  void inject(RawProviderField rawProviderField);",
            "}");

    Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(file);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining("Provider cannot be provided")
        .inFile(file)
        .onLineContaining("interface C");
  }

  @Test
  public void throwExceptionInjectedMethod() {
    JavaFileObject file =
        JavaFileObjects.forSourceLines(
            "test.",
            "package test;",
            "",
            "import dagger.Component;",
            "import javax.inject.Inject;",
            "class SomeClass {",
            "@Inject void inject() throws Exception {}",
            "}");

    Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(file);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining("Methods with @Inject may not throw checked exceptions. "
          + "Please wrap your exceptions in a RuntimeException instead.")
        .inFile(file)
        .onLineContaining("throws Exception");
  }

  @Test
  public void rawFrameworkTypeParameter() {
    JavaFileObject file =
        JavaFileObjects.forSourceLines(
            "test.RawFrameworkTypes",
            "package test;",
            "",
            "import dagger.Component;",
            "import javax.inject.Inject;",
            "import javax.inject.Provider;",
            "",
            "class RawProviderParameter {",
            "  @Inject void methodInjection(Provider rawProviderParameter) {}",
            "}",
            "",
            "@Component",
            "interface C {",
            "  void inject(RawProviderParameter rawProviderParameter);",
            "}");

    Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(file);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining("Provider cannot be provided")
        .inFile(file)
        .onLineContaining("interface C");
  }

  @Test
  public void injectsPrimitive() {
    JavaFileObject injectedType =
        JavaFileObjects.forSourceLines(
            "test.InjectedType",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class InjectedType {",
            "  @Inject InjectedType() {}",
            "",
            "  @Inject int primitiveInt;",
            "  @Inject Integer boxedInt;",
            "}");
    JavaFileObject membersInjector =
        JavaFileObjects.forSourceLines(
            "test.InjectedType_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class InjectedType_MembersInjector ",
            "    implements MembersInjector<InjectedType> {",
            "  private final Provider<Integer> primitiveIntProvider;",
            "  private final Provider<Integer> boxedIntProvider;",
            "",
            "  public InjectedType_MembersInjector(",
            "      Provider<Integer> primitiveIntProvider, Provider<Integer> boxedIntProvider) {",
            "    this.primitiveIntProvider = primitiveIntProvider;",
            "    this.boxedIntProvider = boxedIntProvider;",
            "  }",
            "",
            "  public static MembersInjector<InjectedType> create(",
            "      Provider<Integer> primitiveIntProvider, Provider<Integer> boxedIntProvider) {",
            "    return new InjectedType_MembersInjector(primitiveIntProvider, boxedIntProvider);}",
            "",
            "  @Override",
            "  public void injectMembers(InjectedType instance) {",
            "    injectPrimitiveInt(instance, primitiveIntProvider.get());",
            "    injectBoxedInt(instance, boxedIntProvider.get());",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.InjectedType.primitiveInt\")",
            "  public static void injectPrimitiveInt(Object instance, int primitiveInt) {",
            "    ((InjectedType) instance).primitiveInt = primitiveInt;",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.InjectedType.boxedInt\")",
            "  public static void injectBoxedInt(Object instance, Integer boxedInt) {",
            "    ((InjectedType) instance).boxedInt = boxedInt;",
            "  }",
            "}");
    JavaFileObject factory =
        JavaFileObjects.forSourceLines(
            "test.InjectedType_Factory",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.internal.Factory;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class InjectedType_Factory implements Factory<InjectedType> {",
            "  private final Provider<Integer> primitiveIntProvider;",
            "",
            "  private final Provider<Integer> boxedIntProvider;",
            "",
            "  public InjectedType_Factory(",
            "      Provider<Integer> primitiveIntProvider, Provider<Integer> boxedIntProvider) {",
            "    this.primitiveIntProvider = primitiveIntProvider;",
            "    this.boxedIntProvider = boxedIntProvider;",
            "  }",
            "",
            "  @Override",
            "  public InjectedType get() {",
            "    InjectedType instance = newInstance();",
            "    InjectedType_MembersInjector.injectPrimitiveInt(",
            "        instance, primitiveIntProvider.get());",
            "    InjectedType_MembersInjector.injectBoxedInt(instance, boxedIntProvider.get());",
            "    return instance;",
            "  }",
            "",
            "  public static InjectedType_Factory create(",
            "      Provider<Integer> primitiveIntProvider, Provider<Integer> boxedIntProvider) {",
            "    return new InjectedType_Factory(primitiveIntProvider, boxedIntProvider);",
            "  }",
            "",
            "  public static InjectedType newInstance() {",
            "    return new InjectedType();",
            "  }",
            "}");
    Compilation compilation =
        compilerWithOptions(compilerMode.javacopts()).compile(injectedType);
    assertThat(compilation).succeeded();
    assertThat(compilation)
        .generatedSourceFile("test.InjectedType_MembersInjector")
        .hasSourceEquivalentTo(membersInjector);
    assertThat(compilation)
        .generatedSourceFile("test.InjectedType_Factory")
        .hasSourceEquivalentTo(factory);
  }

  @Test
  public void accessibility() {
    JavaFileObject foo =
        JavaFileObjects.forSourceLines(
            "other.Foo",
            "package other;",
            "",
            "import javax.inject.Inject;",
            "",
            "class Foo {",
            "  @Inject Foo() {}",
            "}");
    JavaFileObject inaccessible =
        JavaFileObjects.forSourceLines(
            "other.Inaccessible",
            "package other;",
            "",
            "import javax.inject.Inject;",
            "",
            "class Inaccessible {",
            "  @Inject Inaccessible() {}",
            "  @Inject Foo foo;",
            "  @Inject void method(Foo foo) {}",
            "}");
    JavaFileObject usesInaccessible =
        JavaFileObjects.forSourceLines(
            "other.UsesInaccessible",
            "package other;",
            "",
            "import javax.inject.Inject;",
            "",
            "public class UsesInaccessible {",
            "  @Inject UsesInaccessible(Inaccessible inaccessible) {}",
            "}");
    JavaFileObject component =
        JavaFileObjects.forSourceLines(
            "test.TestComponent",
            "package test;",
            "",
            "import dagger.Component;",
            "import other.UsesInaccessible;",
            "",
            "@Component",
            "interface TestComponent {",
            "  UsesInaccessible usesInaccessible();",
            "}");

    Compilation compilation =
        compilerWithOptions(compilerMode.javacopts())
            .compile(foo, inaccessible, usesInaccessible, component);
    assertThat(compilation).succeeded();
    assertThat(compilation)
        .generatedSourceFile("other.Inaccessible_MembersInjector")
        .hasSourceEquivalentTo(
            JavaFileObjects.forSourceLines(
                "other.Inaccessible_MembersInjector",
                "package other;",
                "",
                GeneratedLines.generatedImports(
                    "import dagger.MembersInjector;",
                    "import dagger.internal.InjectedFieldSignature;",
                    "import javax.inject.Provider;"),
                "",
                GeneratedLines.generatedAnnotations(),
                "public final class Inaccessible_MembersInjector",
                "    implements MembersInjector<Inaccessible> {",
                "  private final Provider<Foo> fooProvider;",
                "  private final Provider<Foo> fooProvider2;",
                "",
                "  public Inaccessible_MembersInjector(",
                "      Provider<Foo> fooProvider, Provider<Foo> fooProvider2) {",
                "    this.fooProvider = fooProvider;",
                "    this.fooProvider2 = fooProvider2;",
                "  }",
                "",
                "  public static MembersInjector<Inaccessible> create(",
                "      Provider<Foo> fooProvider, Provider<Foo> fooProvider2) {",
                "    return new Inaccessible_MembersInjector(fooProvider, fooProvider2);}",
                "",
                "  @Override",
                "  public void injectMembers(Inaccessible instance) {",
                "    injectFoo(instance, fooProvider.get());",
                "    injectMethod(instance, fooProvider2.get());",
                "  }",
                "",
                "  @InjectedFieldSignature(\"other.Inaccessible.foo\")",
                "  public static void injectFoo(Object instance, Object foo) {",
                "    ((Inaccessible) instance).foo = (Foo) foo;",
                "  }",
                "",
                "  public static void injectMethod(Object instance, Object foo) {",
                "    ((Inaccessible) instance).method((Foo) foo);",
                "  }",
                "}"));
    JavaFileObject generatedComponent =
        JavaFileObjects.forSourceLines(
            "test.DaggerTestComponent",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import com.google.errorprone.annotations.CanIgnoreReturnValue;",
                "import other.Foo_Factory;",
                "import other.Inaccessible_Factory;",
                "import other.Inaccessible_MembersInjector;",
                "import other.UsesInaccessible;",
                "import other.UsesInaccessible_Factory;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "final class DaggerTestComponent implements TestComponent {",
            "  private Object inaccessible() {",
            "    return injectInaccessible(Inaccessible_Factory.newInstance());",
            "  }",
            "",
            "  @Override",
            "  public UsesInaccessible usesInaccessible() {",
            "    return UsesInaccessible_Factory.newInstance(",
            "        inaccessible());",
            "  }",
            "",
            // TODO(ronshapiro): if possible, it would be great to rename "instance", but we
            // need to make sure that this doesn't conflict with any framework field in this or
            // any parent component
            "  @CanIgnoreReturnValue",
            "  private Object injectInaccessible(Object instance) {",
            "    Inaccessible_MembersInjector.injectFoo(instance, Foo_Factory.newInstance());",
            "    Inaccessible_MembersInjector.injectMethod(instance, Foo_Factory.newInstance());",
            "    return instance;",
            "  }",
            "}");
    assertThat(compilation)
        .generatedSourceFile("test.DaggerTestComponent")
        .containsElementsIn(generatedComponent);
  }

  @Test
  public void accessibleRawType_ofInaccessibleType() {
    JavaFileObject inaccessible =
        JavaFileObjects.forSourceLines(
            "other.Inaccessible",
            "package other;",
            "",
            "class Inaccessible {}");
    JavaFileObject inaccessiblesModule =
        JavaFileObjects.forSourceLines(
            "other.InaccessiblesModule",
            "package other;",
            "",
            "import dagger.Module;",
            "import dagger.Provides;",
            "import java.util.ArrayList;",
            "import java.util.List;",
            "import javax.inject.Provider;",
            "import javax.inject.Singleton;",
            "",
            "@Module",
            "public class InaccessiblesModule {",
            // force Provider initialization
            "  @Provides @Singleton static List<Inaccessible> inaccessibles() {",
            "    return new ArrayList<>();",
            "  }",
            "}");
    JavaFileObject usesInaccessibles =
        JavaFileObjects.forSourceLines(
            "other.UsesInaccessibles",
            "package other;",
            "",
            "import java.util.List;",
            "import javax.inject.Inject;",
            "",
            "public class UsesInaccessibles {",
            "  @Inject UsesInaccessibles() {}",
            "  @Inject List<Inaccessible> inaccessibles;",
            "}");
    JavaFileObject component =
        JavaFileObjects.forSourceLines(
            "test.TestComponent",
            "package test;",
            "",
            "import dagger.Component;",
            "import javax.inject.Singleton;",
            "import other.UsesInaccessibles;",
            "",
            "@Singleton",
            "@Component(modules = other.InaccessiblesModule.class)",
            "interface TestComponent {",
            "  UsesInaccessibles usesInaccessibles();",
            "}");

    Compilation compilation =
        compilerWithOptions(compilerMode.javacopts())
            .compile(inaccessible, inaccessiblesModule, usesInaccessibles, component);
    assertThat(compilation).succeeded();
    JavaFileObject generatedComponent =
        compilerMode
            .javaFileBuilder("test.DaggerTestComponent")
            .addLines(
                "package test;",
                "",
                GeneratedLines.generatedImports(
                    "import com.google.errorprone.annotations.CanIgnoreReturnValue;",
                    "import other.InaccessiblesModule;",
                    "import other.InaccessiblesModule_InaccessiblesFactory;",
                    "import other.UsesInaccessibles;",
                    "import other.UsesInaccessibles_Factory;",
                    "import other.UsesInaccessibles_MembersInjector;"),
                "",
                GeneratedLines.generatedAnnotations(),
                "final class DaggerTestComponent implements TestComponent {")
            .addLinesIn(
                FAST_INIT_MODE,
                "  private volatile Object listOfInaccessible = new MemoizedSentinel();",
                "",
                "  private List listOfInaccessible() {",
                "    Object local = listOfInaccessible;",
                "    if (local instanceof MemoizedSentinel) {",
                "      synchronized (local) {",
                "        local = listOfInaccessible;",
                "        if (local instanceof MemoizedSentinel) {",
                "          local = InaccessiblesModule_InaccessiblesFactory.inaccessibles();",
                "          listOfInaccessible =",
                "              DoubleCheck.reentrantCheck(listOfInaccessible, local);",
                "        }",
                "      }",
                "    }",
                "    return (List) local;",
                "  }")
            .addLinesIn(
                DEFAULT_MODE,
                "  @SuppressWarnings(\"rawtypes\")",
                "  private Provider inaccessiblesProvider;",
                "",
                "  @SuppressWarnings(\"unchecked\")",
                "  private void initialize() {",
                "    this.inaccessiblesProvider =",
                "        DoubleCheck.provider(InaccessiblesModule_InaccessiblesFactory.create());",
                "  }")
            .addLines(
                "",
                "  @Override",
                "  public UsesInaccessibles usesInaccessibles() {",
                "    return injectUsesInaccessibles(",
                "        UsesInaccessibles_Factory.newInstance());",
                "  }",
                "",
                "  @CanIgnoreReturnValue",
                "  private UsesInaccessibles injectUsesInaccessibles(",
                "        UsesInaccessibles instance) {",
                "    UsesInaccessibles_MembersInjector.injectInaccessibles(")
            .addLinesIn(
                FAST_INIT_MODE,
                "        instance, (List) listOfInaccessible());")
            .addLinesIn(
                DEFAULT_MODE,
                "        instance, (List) inaccessiblesProvider.get());")
            .addLines(
                "    return instance;",
                "  }",
                "}")
            .build();
    assertThat(compilation)
        .generatedSourceFile("test.DaggerTestComponent")
        .containsElementsIn(generatedComponent);
  }

  @Test
  public void publicSupertypeHiddenSubtype() {
    JavaFileObject foo =
        JavaFileObjects.forSourceLines(
            "other.Foo",
            "package other;",
            "",
            "import javax.inject.Inject;",
            "",
            "class Foo {",
            "  @Inject Foo() {}",
            "}");
    JavaFileObject supertype =
        JavaFileObjects.forSourceLines(
            "other.Supertype",
            "package other;",
            "",
            "import javax.inject.Inject;",
            "",
            "public class Supertype<T> {",
            "  @Inject T t;",
            "}");
    JavaFileObject subtype =
        JavaFileObjects.forSourceLines(
            "other.Subtype",
            "package other;",
            "",
            "import javax.inject.Inject;",
            "",
            "class Subtype extends Supertype<Foo> {",
            "  @Inject Subtype() {}",
            "}");
    JavaFileObject injectsSubtype =
        JavaFileObjects.forSourceLines(
            "other.InjectsSubtype",
            "package other;",
            "",
            "import javax.inject.Inject;",
            "",
            "public class InjectsSubtype {",
            "  @Inject InjectsSubtype(Subtype s) {}",
            "}");
    JavaFileObject component =
        JavaFileObjects.forSourceLines(
            "test.TestComponent",
            "package test;",
            "",
            "import dagger.Component;",
            "",
            "@Component",
            "interface TestComponent {",
            "  other.InjectsSubtype injectsSubtype();",
            "}");

    Compilation compilation =
        compilerWithOptions(compilerMode.javacopts())
            .compile(foo, supertype, subtype, injectsSubtype, component);
    assertThat(compilation).succeeded();
    JavaFileObject generatedComponent =
        JavaFileObjects.forSourceLines(
            "test.DaggerTestComponent",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import com.google.errorprone.annotations.CanIgnoreReturnValue;",
                "import other.Foo_Factory;",
                "import other.InjectsSubtype;",
                "import other.InjectsSubtype_Factory;",
                "import other.Subtype_Factory;",
                "import other.Supertype;",
                "import other.Supertype_MembersInjector;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "final class DaggerTestComponent implements TestComponent {",
            "  private Object subtype() {",
            "    return injectSubtype(Subtype_Factory.newInstance());",
            "  }",
            "",
            "  @Override",
            "  public InjectsSubtype injectsSubtype() {",
            "    return InjectsSubtype_Factory.newInstance(subtype());",
            "  }",
            "",
            "  @CanIgnoreReturnValue",
            "  private Object injectSubtype(Object instance) {",
            "    Supertype_MembersInjector.injectT(",
            "        (Supertype) instance, Foo_Factory.newInstance());",
            "    return instance;",
            "  }",
            "}");

    assertThat(compilation)
        .generatedSourceFile("test.DaggerTestComponent")
        .containsElementsIn(generatedComponent);
  }

  // Shows that we shouldn't create a members injector for a type that doesn't have
  // @Inject fields or @Inject constructor even if it extends and is extended by types that do.
  @Test
  public void middleClassNoFieldInjection() {
    JavaFileObject classA =
        JavaFileObjects.forSourceLines(
            "test.A",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class A extends B {",
            "  @Inject String valueA;",
            "}");
    JavaFileObject classB =
        JavaFileObjects.forSourceLines(
            "test.B",
            "package test;",
            "",
            "class B extends C {",
            "}");
    JavaFileObject classC =
        JavaFileObjects.forSourceLines(
            "test.C",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class C { ",
            "  @Inject String valueC;",
            "}");
    JavaFileObject expectedAMembersInjector =
        JavaFileObjects.forSourceLines(
            "test.A_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class A_MembersInjector implements MembersInjector<A> {",
            "  private final Provider<String> valueCProvider;",
            "  private final Provider<String> valueAProvider;",
            "",
            "  public A_MembersInjector(",
            "        Provider<String> valueCProvider, Provider<String> valueAProvider) {",
            "    this.valueCProvider = valueCProvider;",
            "    this.valueAProvider = valueAProvider;",
            "  }",
            "",
            "  public static MembersInjector<A> create(",
            "      Provider<String> valueCProvider, Provider<String> valueAProvider) {",
            "    return new A_MembersInjector(valueCProvider, valueAProvider);",
            "  }",
            "",
            "  @Override",
            "  public void injectMembers(A instance) {",
            "    C_MembersInjector.injectValueC(instance, valueCProvider.get());",
            "    injectValueA(instance, valueAProvider.get());",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.A.valueA\")",
            "  public static void injectValueA(Object instance, String valueA) {",
            "    ((A) instance).valueA = valueA;",
            "  }",
            "}");

    JavaFileObject expectedCMembersInjector =
        JavaFileObjects.forSourceLines(
            "test.C_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class C_MembersInjector implements MembersInjector<C> {",
            "  private final Provider<String> valueCProvider;",
            "",
            "  public C_MembersInjector(Provider<String> valueCProvider) {",
            "    this.valueCProvider = valueCProvider;",
            "  }",
            "",
            "  public static MembersInjector<C> create(",
            "      Provider<String> valueCProvider) {",
            "    return new C_MembersInjector(valueCProvider);",
            "  }",
            "",
            "  @Override",
            "  public void injectMembers(C instance) {",
            "    injectValueC(instance, valueCProvider.get());",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.C.valueC\")",
            "  public static void injectValueC(Object instance, String valueC) {",
            "    ((C) instance).valueC = valueC;",
            "  }",
            "}");


    Compilation compilation =
        compilerWithOptions(compilerMode.javacopts())
            .compile(classA, classB, classC);
    assertThat(compilation).succeeded();
    assertThat(compilation)
        .generatedSourceFile("test.A_MembersInjector")
        .hasSourceEquivalentTo(expectedAMembersInjector);
    assertThat(compilation)
        .generatedSourceFile("test.C_MembersInjector")
        .hasSourceEquivalentTo(expectedCMembersInjector);

    try {
      assertThat(compilation).generatedSourceFile("test.B_MembersInjector");
      // Can't throw an assertion error since it would be caught.
      throw new IllegalStateException("Test generated a B_MembersInjector");
    } catch (AssertionError expected) {
    }
  }

  // Shows that we do generate a MembersInjector for a type that has an @Inject
  // constructor and that extends a type with @Inject fields, even if it has no local field
  // injection sites
  // TODO(erichang): Are these even used anymore?
  @Test
  public void testConstructorInjectedFieldInjection() {
    JavaFileObject classA =
        JavaFileObjects.forSourceLines(
            "test.A",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class A extends B {",
            "  @Inject A() {}",
            "}");
    JavaFileObject classB =
        JavaFileObjects.forSourceLines(
            "test.B",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class B { ",
            "  @Inject String valueB;",
            "}");
    JavaFileObject expectedAMembersInjector =
        JavaFileObjects.forSourceLines(
            "test.A_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class A_MembersInjector implements MembersInjector<A> {",
            "  private final Provider<String> valueBProvider;",
            "",
            "  public A_MembersInjector(Provider<String> valueBProvider) {",
            "    this.valueBProvider = valueBProvider;",
            "  }",
            "",
            "  public static MembersInjector<A> create(Provider<String> valueBProvider) {",
            "    return new A_MembersInjector(valueBProvider);",
            "  }",
            "",
            "  @Override",
            "  public void injectMembers(A instance) {",
            "    B_MembersInjector.injectValueB(instance, valueBProvider.get());",
            "  }",
            "}");

    JavaFileObject expectedBMembersInjector =
        JavaFileObjects.forSourceLines(
            "test.B_MembersInjector",
            "package test;",
            "",
            GeneratedLines.generatedImports(
                "import dagger.MembersInjector;",
                "import dagger.internal.InjectedFieldSignature;",
                "import javax.inject.Provider;"),
            "",
            GeneratedLines.generatedAnnotations(),
            "public final class B_MembersInjector implements MembersInjector<B> {",
            "  private final Provider<String> valueBProvider;",
            "",
            "  public B_MembersInjector(Provider<String> valueBProvider) {",
            "    this.valueBProvider = valueBProvider;",
            "  }",
            "",
            "  public static MembersInjector<B> create(",
            "      Provider<String> valueBProvider) {",
            "    return new B_MembersInjector(valueBProvider);",
            "  }",
            "",
            "  @Override",
            "  public void injectMembers(B instance) {",
            "    injectValueB(instance, valueBProvider.get());",
            "  }",
            "",
            "  @InjectedFieldSignature(\"test.B.valueB\")",
            "  public static void injectValueB(Object instance, String valueB) {",
            "    ((B) instance).valueB = valueB;",
            "  }",
            "}");


    Compilation compilation =
        compilerWithOptions(compilerMode.javacopts())
            .compile(classA, classB);
    assertThat(compilation).succeeded();
    assertThat(compilation)
        .generatedSourceFile("test.A_MembersInjector")
        .hasSourceEquivalentTo(expectedAMembersInjector);
    assertThat(compilation)
        .generatedSourceFile("test.B_MembersInjector")
        .hasSourceEquivalentTo(expectedBMembersInjector);
  }
}
