/* * Copyright (C) 2014 The Dagger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static com.google.testing.compile.Compiler.javac; 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 dagger.internal.codegen.Compilers.daggerCompiler; import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import com.google.auto.common.MoreElements; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Sets; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; import dagger.MembersInjector; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.inject.Inject; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; 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 ComponentProcessorTest { @Parameters(name = "{0}") public static Collection parameters() { return CompilerMode.TEST_PARAMETERS; } private final CompilerMode compilerMode; public ComponentProcessorTest(CompilerMode compilerMode) { this.compilerMode = compilerMode; } @Test public void doubleBindingFromResolvedModules() { JavaFileObject parent = JavaFileObjects.forSourceLines("test.ParentModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import java.util.List;", "", "@Module", "abstract class ParentModule {", " @Provides List provideListB(A a) { return null; }", "}"); JavaFileObject child = JavaFileObjects.forSourceLines("test.ChildModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "class ChildNumberModule extends ParentModule {", " @Provides Integer provideInteger() { return null; }", "}"); JavaFileObject another = JavaFileObjects.forSourceLines("test.AnotherModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import java.util.List;", "", "@Module", "class AnotherModule {", " @Provides List provideListOfInteger() { return null; }", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.BadComponent", "package test;", "", "import dagger.Component;", "import java.util.List;", "", "@Component(modules = {ChildNumberModule.class, AnotherModule.class})", "interface BadComponent {", " List listOfInteger();", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(parent, child, another, componentFile); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining("List is bound multiple times"); assertThat(compilation) .hadErrorContaining("@Provides List ChildNumberModule.provideListB(Integer)"); assertThat(compilation) .hadErrorContaining("@Provides List AnotherModule.provideListOfInteger()"); } @Test public void privateNestedClassWithWarningThatIsAnErrorInComponent() { JavaFileObject outerClass = JavaFileObjects.forSourceLines("test.OuterClass", "package test;", "", "import javax.inject.Inject;", "", "final class OuterClass {", " @Inject OuterClass(InnerClass innerClass) {}", "", " private static final class InnerClass {", " @Inject InnerClass() {}", " }", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.BadComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface BadComponent {", " OuterClass outerClass();", "}"); Compilation compilation = compilerWithOptions( compilerMode.javacopts().append("-Adagger.privateMemberValidation=WARNING")) .compile(outerClass, componentFile); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining("Dagger does not support injection into private classes"); } @Test public void simpleComponent() { 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 dagger.Lazy;", "import javax.inject.Provider;", "", "@Component", "interface SimpleComponent {", " SomeInjectableType someInjectableType();", " Lazy lazySomeInjectableType();", " Provider someInjectableTypeProvider();", "}"); JavaFileObject generatedComponent = compilerMode .javaFileBuilder("test.DaggerSimpleComponent") .addLines( "package test;", "", "import dagger.Lazy;", "import dagger.internal.DoubleCheck;", IMPORT_GENERATED_ANNOTATION, "import javax.inject.Provider;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerSimpleComponent implements SimpleComponent {") .addLinesIn( FAST_INIT_MODE, " private volatile Provider someInjectableTypeProvider;") .addLines( " private DaggerSimpleComponent() {}", "", " public static Builder builder() {", " return new Builder();", " }", "", " public static SimpleComponent create() {", " return new Builder().build();", " }", "", " @Override", " public SomeInjectableType someInjectableType() {", " return new SomeInjectableType();", " }", "", " @Override", " public Lazy lazySomeInjectableType() {") .addLinesIn( DEFAULT_MODE, // " return DoubleCheck.lazy(SomeInjectableType_Factory.create());") .addLinesIn( FAST_INIT_MODE, " return DoubleCheck.lazy(someInjectableTypeProvider());") .addLines( " }", "", " @Override", " public Provider someInjectableTypeProvider() {") .addLinesIn( DEFAULT_MODE, // " return SomeInjectableType_Factory.create();") .addLinesIn( FAST_INIT_MODE, // " Object local = someInjectableTypeProvider;", " if (local == null) {", " local = new SwitchingProvider<>(0);", " someInjectableTypeProvider = (Provider) local;", " }", " return (Provider) local;") .addLines( " }", "", " static final class Builder {", " private Builder() {}", "", " public SimpleComponent build() {", " return new DaggerSimpleComponent();", " }", " }") .addLinesIn( FAST_INIT_MODE, " private final class SwitchingProvider implements Provider {", " private final int id;", "", " SwitchingProvider(int id) {", " this.id = id;", " }", "", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", " case 0: return (T) new SomeInjectableType();", " default: throw new AssertionError(id);", " }", " }", " }") .build(); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(injectableTypeFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerSimpleComponent") .hasSourceEquivalentTo(generatedComponent); } @Test public void componentWithScope() { JavaFileObject injectableTypeFile = JavaFileObjects.forSourceLines("test.SomeInjectableType", "package test;", "", "import javax.inject.Inject;", "import javax.inject.Singleton;", "", "@Singleton", "final class SomeInjectableType {", " @Inject SomeInjectableType() {}", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent", "package test;", "", "import dagger.Component;", "import dagger.Lazy;", "import javax.inject.Provider;", "import javax.inject.Singleton;", "", "@Singleton", "@Component", "interface SimpleComponent {", " SomeInjectableType someInjectableType();", " Lazy lazySomeInjectableType();", " Provider someInjectableTypeProvider();", "}"); JavaFileObject generatedComponent = compilerMode .javaFileBuilder("test.DaggerSimpleComponent") .addLines( "package test;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerSimpleComponent implements SimpleComponent {") .addLinesIn( FAST_INIT_MODE, " private volatile Object someInjectableType = new MemoizedSentinel();", " private volatile Provider someInjectableTypeProvider;") .addLinesIn( DEFAULT_MODE, " private Provider someInjectableTypeProvider;", "", " @SuppressWarnings(\"unchecked\")", " private void initialize() {", " this.someInjectableTypeProvider =", " DoubleCheck.provider(SomeInjectableType_Factory.create());", " }", "") .addLines( " @Override", // " public SomeInjectableType someInjectableType() {") .addLinesIn( FAST_INIT_MODE, " Object local = someInjectableType;", " if (local instanceof MemoizedSentinel) {", " synchronized (local) {", " local = someInjectableType;", " if (local instanceof MemoizedSentinel) {", " local = new SomeInjectableType();", " someInjectableType =", " DoubleCheck.reentrantCheck(someInjectableType, local);", " }", " }", " }", " return (SomeInjectableType) local;") .addLinesIn( DEFAULT_MODE, // " return someInjectableTypeProvider.get();") .addLines( " }", "", " @Override", " public Lazy lazySomeInjectableType() {") .addLinesIn( DEFAULT_MODE, // " return DoubleCheck.lazy(someInjectableTypeProvider);") .addLinesIn( FAST_INIT_MODE, " return DoubleCheck.lazy(someInjectableTypeProvider());") .addLines( " }", "", " @Override", " public Provider someInjectableTypeProvider() {") .addLinesIn( FAST_INIT_MODE, // " Object local = someInjectableTypeProvider;", " if (local == null) {", " local = new SwitchingProvider<>(0);", " someInjectableTypeProvider = (Provider) local;", " }", " return (Provider) local;") .addLinesIn( DEFAULT_MODE, // " return someInjectableTypeProvider;") .addLines( // " }") .addLinesIn( FAST_INIT_MODE, " private final class SwitchingProvider implements Provider {", " private final int id;", "", " SwitchingProvider(int id) {", " this.id = id;", " }", "", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", " case 0: return (T) DaggerSimpleComponent.this.someInjectableType();", " default: throw new AssertionError(id);", " }", " }", " }") .build(); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(injectableTypeFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerSimpleComponent") .containsElementsIn(generatedComponent); } @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 generatedComponent = compilerMode .javaFileBuilder("test.DaggerOuterType_SimpleComponent") .addLines( "package test;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerOuterType_SimpleComponent", " implements OuterType.SimpleComponent {", " private DaggerOuterType_SimpleComponent() {}", "", " @Override", " public OuterType.A a() {", " return new OuterType.A();", " }", "", " @Override", " public void inject(OuterType.B b) {", " injectB(b);", " }", "", " @CanIgnoreReturnValue", " private OuterType.B injectB(OuterType.B instance) {", " OuterType_B_MembersInjector.injectA(instance, new OuterType.A());", " return instance;", " }", "}") .build(); Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(nestedTypesFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerOuterType_SimpleComponent") .containsElementsIn(generatedComponent); } @Test public void componentWithModule() { JavaFileObject aFile = JavaFileObjects.forSourceLines("test.A", "package test;", "", "import javax.inject.Inject;", "", "final class A {", " @Inject A(B b) {}", "}"); JavaFileObject bFile = JavaFileObjects.forSourceLines("test.B", "package test;", "", "interface B {}"); JavaFileObject cFile = JavaFileObjects.forSourceLines("test.C", "package test;", "", "import javax.inject.Inject;", "", "final class C {", " @Inject C() {}", "}"); JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "final class TestModule {", " @Provides B b(C c) { return null; }", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", "import dagger.Component;", "import javax.inject.Provider;", "", "@Component(modules = TestModule.class)", "interface TestComponent {", " A a();", "}"); JavaFileObject generatedComponent = compilerMode .javaFileBuilder("test.DaggerTestComponent") .addLines( "package test;", "", "import dagger.internal.Preconditions;", IMPORT_GENERATED_ANNOTATION, "", GENERATED_CODE_ANNOTATIONS, "final class DaggerTestComponent implements TestComponent {", " private final TestModule testModule;", "", " private DaggerTestComponent(TestModule testModuleParam) {", " this.testModule = testModuleParam;", " }", "", " private B b() {", " return TestModule_BFactory.b(testModule, new C());", " }", "", " @Override", " public A a() {", " return new A(b());", " }", "", " static final class Builder {", " private TestModule testModule;", "", " public Builder testModule(TestModule testModule) {", " this.testModule = Preconditions.checkNotNull(testModule);", " return this;", " }", "", " public TestComponent build() {", " if (testModule == null) {", " this.testModule = new TestModule();", " }", " return new DaggerTestComponent(testModule);", " }", " }", "}") .build(); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(aFile, bFile, cFile, moduleFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") .containsElementsIn(generatedComponent); } @Test public void componentWithAbstractModule() { JavaFileObject aFile = JavaFileObjects.forSourceLines( "test.A", "package test;", "", "import javax.inject.Inject;", "", "final class A {", " @Inject A(B b) {}", "}"); JavaFileObject bFile = JavaFileObjects.forSourceLines("test.B", "package test;", "", "interface B {}"); JavaFileObject cFile = JavaFileObjects.forSourceLines( "test.C", "package test;", "", "import javax.inject.Inject;", "", "final class C {", " @Inject C() {}", "}"); JavaFileObject moduleFile = JavaFileObjects.forSourceLines( "test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "abstract class TestModule {", " @Provides static B b(C c) { return null; }", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = TestModule.class)", "interface TestComponent {", " A a();", "}"); JavaFileObject generatedComponent = compilerMode .javaFileBuilder("test.DaggerTestComponent") .addLines( "package test;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerTestComponent implements TestComponent {", " private B b() {", " return TestModule_BFactory.b(new C());", " }", "", " @Override", " public A a() {", " return new A(b());", " }", "}") .build(); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(aFile, bFile, cFile, moduleFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") .containsElementsIn(generatedComponent); } @Test public void transitiveModuleDeps() { JavaFileObject always = JavaFileObjects.forSourceLines("test.AlwaysIncluded", "package test;", "", "import dagger.Module;", "", "@Module", "final class AlwaysIncluded {}"); JavaFileObject testModule = JavaFileObjects.forSourceLines("test.TestModule", "package test;", "", "import dagger.Module;", "", "@Module(includes = {DepModule.class, AlwaysIncluded.class})", "final class TestModule extends ParentTestModule {}"); JavaFileObject parentTest = JavaFileObjects.forSourceLines("test.ParentTestModule", "package test;", "", "import dagger.Module;", "", "@Module(includes = {ParentTestIncluded.class, AlwaysIncluded.class})", "class ParentTestModule {}"); JavaFileObject parentTestIncluded = JavaFileObjects.forSourceLines("test.ParentTestIncluded", "package test;", "", "import dagger.Module;", "", "@Module(includes = AlwaysIncluded.class)", "final class ParentTestIncluded {}"); JavaFileObject depModule = JavaFileObjects.forSourceLines("test.TestModule", "package test;", "", "import dagger.Module;", "", "@Module(includes = {RefByDep.class, AlwaysIncluded.class})", "final class DepModule extends ParentDepModule {}"); JavaFileObject refByDep = JavaFileObjects.forSourceLines("test.RefByDep", "package test;", "", "import dagger.Module;", "", "@Module(includes = AlwaysIncluded.class)", "final class RefByDep extends ParentDepModule {}"); JavaFileObject parentDep = JavaFileObjects.forSourceLines("test.ParentDepModule", "package test;", "", "import dagger.Module;", "", "@Module(includes = {ParentDepIncluded.class, AlwaysIncluded.class})", "class ParentDepModule {}"); JavaFileObject parentDepIncluded = JavaFileObjects.forSourceLines("test.ParentDepIncluded", "package test;", "", "import dagger.Module;", "", "@Module(includes = AlwaysIncluded.class)", "final class ParentDepIncluded {}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = TestModule.class)", "interface TestComponent {", "}"); // Generated code includes all includes, but excludes the parent modules. // The "always" module should only be listed once. JavaFileObject generatedComponent = JavaFileObjects.forSourceLines( "test.DaggerTestComponent", "package test;", "", "import dagger.internal.Preconditions;", IMPORT_GENERATED_ANNOTATION, "", GENERATED_CODE_ANNOTATIONS, "final class DaggerTestComponent implements TestComponent {", " static final class Builder {", "", " @Deprecated", " public Builder testModule(TestModule testModule) {", " Preconditions.checkNotNull(testModule)", " return this;", " }", "", " @Deprecated", " public Builder parentTestIncluded(ParentTestIncluded parentTestIncluded) {", " Preconditions.checkNotNull(parentTestIncluded)", " return this;", " }", "", " @Deprecated", " public Builder alwaysIncluded(AlwaysIncluded alwaysIncluded) {", " Preconditions.checkNotNull(alwaysIncluded)", " return this;", " }", "", " @Deprecated", " public Builder depModule(DepModule depModule) {", " Preconditions.checkNotNull(depModule)", " return this;", " }", "", " @Deprecated", " public Builder parentDepIncluded(ParentDepIncluded parentDepIncluded) {", " Preconditions.checkNotNull(parentDepIncluded)", " return this;", " }", "", " @Deprecated", " public Builder refByDep(RefByDep refByDep) {", " Preconditions.checkNotNull(refByDep)", " return this;", " }", "", " public TestComponent build() {", " return new DaggerTestComponent();", " }", " }", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile( always, testModule, parentTest, parentTestIncluded, depModule, refByDep, parentDep, parentDepIncluded, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") .containsElementsIn(generatedComponent); } @Test public void generatedTransitiveModule() { JavaFileObject rootModule = JavaFileObjects.forSourceLines("test.RootModule", "package test;", "", "import dagger.Module;", "", "@Module(includes = GeneratedModule.class)", "final class RootModule {}"); JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = RootModule.class)", "interface TestComponent {}"); assertThat( compilerWithOptions(compilerMode.javacopts()).compile(rootModule, component)) .failed(); assertThat( daggerCompiler( new GeneratingProcessor( "test.GeneratedModule", "package test;", "", "import dagger.Module;", "", "@Module", "final class GeneratedModule {}")) .compile(rootModule, component)) .succeeded(); } @Test public void generatedModuleInSubcomponent() { JavaFileObject subcomponent = JavaFileObjects.forSourceLines( "test.ChildComponent", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent(modules = GeneratedModule.class)", "interface ChildComponent {}"); JavaFileObject component = JavaFileObjects.forSourceLines( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface TestComponent {", " ChildComponent childComponent();", "}"); assertThat( compilerWithOptions(compilerMode.javacopts()).compile(subcomponent, component)) .failed(); assertThat( daggerCompiler( new GeneratingProcessor( "test.GeneratedModule", "package test;", "", "import dagger.Module;", "", "@Module", "final class GeneratedModule {}")) .compile(subcomponent, component)) .succeeded(); } @Test public void subcomponentNotGeneratedIfNotUsedInGraph() { JavaFileObject component = JavaFileObjects.forSourceLines( "test.Parent", "package test;", "", "import dagger.Component;", "", "@Component(modules = ParentModule.class)", "interface Parent {", " String notSubcomponent();", "}"); JavaFileObject module = JavaFileObjects.forSourceLines( "test.Parent", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module(subcomponents = Child.class)", "class ParentModule {", " @Provides static String notSubcomponent() { return new String(); }", "}"); JavaFileObject subcomponent = JavaFileObjects.forSourceLines( "test.Child", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent", "interface Child {", " @Subcomponent.Builder", " interface Builder {", " Child build();", " }", "}"); JavaFileObject generatedComponent = JavaFileObjects.forSourceLines( "test.DaggerParent", "package test;", "", "import dagger.internal.Preconditions;", IMPORT_GENERATED_ANNOTATION, "", GENERATED_CODE_ANNOTATIONS, "final class DaggerParent implements Parent {", "", " private DaggerParent() {}", "", " public static Builder builder() {", " return new Builder();", " }", "", " public static Parent create() {", " return new Builder().build();", " }", "", " @Override", " public String notSubcomponent() {", " return ParentModule_NotSubcomponentFactory.notSubcomponent();", " }", "", " static final class Builder {", "", " private Builder() {}", "", " @Deprecated", " public Builder parentModule(ParentModule parentModule) {", " Preconditions.checkNotNull(parentModule);", " return this;", " }", "", " public Parent build() {", " return new DaggerParent();", " }", " }", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(component, module, subcomponent); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerParent") .hasSourceEquivalentTo(generatedComponent); } @Test public void testDefaultPackage() { JavaFileObject aClass = JavaFileObjects.forSourceLines("AClass", "class AClass {}"); JavaFileObject bClass = JavaFileObjects.forSourceLines("BClass", "import javax.inject.Inject;", "", "class BClass {", " @Inject BClass(AClass a) {}", "}"); JavaFileObject aModule = JavaFileObjects.forSourceLines("AModule", "import dagger.Module;", "import dagger.Provides;", "", "@Module class AModule {", " @Provides AClass aClass() {", " return new AClass();", " }", "}"); JavaFileObject component = JavaFileObjects.forSourceLines("SomeComponent", "import dagger.Component;", "", "@Component(modules = AModule.class)", "interface SomeComponent {", " BClass bClass();", "}"); assertThat( compilerWithOptions(compilerMode.javacopts()) .compile(aModule, aClass, bClass, component)) .succeeded(); } @Test public void membersInjection() { JavaFileObject injectableTypeFile = JavaFileObjects.forSourceLines("test.SomeInjectableType", "package test;", "", "import javax.inject.Inject;", "", "final class SomeInjectableType {", " @Inject SomeInjectableType() {}", "}"); JavaFileObject injectedTypeFile = JavaFileObjects.forSourceLines("test.SomeInjectedType", "package test;", "", "import javax.inject.Inject;", "", "final class SomeInjectedType {", " @Inject SomeInjectableType injectedField;", " SomeInjectedType() {}", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent", "package test;", "", "import dagger.Component;", "import dagger.Lazy;", "import javax.inject.Provider;", "", "@Component", "interface SimpleComponent {", " void inject(SomeInjectedType instance);", " SomeInjectedType injectAndReturn(SomeInjectedType instance);", "}"); JavaFileObject generatedComponent = compilerMode .javaFileBuilder("test.DaggerSimpleComponent") .addLines( "package test;", "", "import com.google.errorprone.annotations.CanIgnoreReturnValue;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerSimpleComponent implements SimpleComponent {", " @Override", " public void inject(SomeInjectedType instance) {", " injectSomeInjectedType(instance);", " }", "", " @Override", " public SomeInjectedType injectAndReturn(SomeInjectedType instance) {", " return injectSomeInjectedType(instance);", " }", "", " @CanIgnoreReturnValue", " private SomeInjectedType injectSomeInjectedType(SomeInjectedType instance) {", " SomeInjectedType_MembersInjector.injectInjectedField(", " instance, new SomeInjectableType());", " return instance;", " }", "}") .build(); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(injectableTypeFile, injectedTypeFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerSimpleComponent") .containsElementsIn(generatedComponent); } @Test public void componentInjection() { JavaFileObject injectableTypeFile = JavaFileObjects.forSourceLines("test.SomeInjectableType", "package test;", "", "import javax.inject.Inject;", "", "final class SomeInjectableType {", " @Inject SomeInjectableType(SimpleComponent component) {}", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent", "package test;", "", "import dagger.Component;", "import dagger.Lazy;", "import javax.inject.Provider;", "", "@Component", "interface SimpleComponent {", " SomeInjectableType someInjectableType();", " Provider selfProvider();", "}"); JavaFileObject generatedComponent = JavaFileObjects.forSourceLines( "test.DaggerSimpleComponent", "package test;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerSimpleComponent implements SimpleComponent {", " private Provider simpleComponentProvider;", "", " @SuppressWarnings(\"unchecked\")", " private void initialize() {", " this.simpleComponentProvider = InstanceFactory.create((SimpleComponent) this);", " }", "", " @Override", " public SomeInjectableType someInjectableType() {", " return new SomeInjectableType(this)", " }", "", " @Override", " public Provider selfProvider() {", " return simpleComponentProvider;", " }", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(injectableTypeFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerSimpleComponent") .containsElementsIn(generatedComponent); } @Test public void membersInjectionInsideProvision() { JavaFileObject injectableTypeFile = JavaFileObjects.forSourceLines("test.SomeInjectableType", "package test;", "", "import javax.inject.Inject;", "", "final class SomeInjectableType {", " @Inject SomeInjectableType() {}", "}"); JavaFileObject injectedTypeFile = JavaFileObjects.forSourceLines("test.SomeInjectedType", "package test;", "", "import javax.inject.Inject;", "", "final class SomeInjectedType {", " @Inject SomeInjectableType injectedField;", " @Inject SomeInjectedType() {}", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface SimpleComponent {", " SomeInjectedType createAndInject();", "}"); JavaFileObject generatedComponent = compilerMode .javaFileBuilder("test.DaggerSimpleComponent") .addLines( "package test;", "", "import com.google.errorprone.annotations.CanIgnoreReturnValue;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerSimpleComponent implements SimpleComponent {", " @Override", " public SomeInjectedType createAndInject() {", " return injectSomeInjectedType(", " SomeInjectedType_Factory.newInstance());", " }", "", " @CanIgnoreReturnValue", " private SomeInjectedType injectSomeInjectedType(SomeInjectedType instance) {", " SomeInjectedType_MembersInjector.injectInjectedField(", " instance, new SomeInjectableType());", " return instance;", " }", "}") .build(); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(injectableTypeFile, injectedTypeFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerSimpleComponent") .containsElementsIn(generatedComponent); } @Test public void componentDependency() { JavaFileObject aFile = JavaFileObjects.forSourceLines("test.A", "package test;", "", "import javax.inject.Inject;", "", "final class A {", " @Inject A() {}", "}"); JavaFileObject bFile = JavaFileObjects.forSourceLines("test.B", "package test;", "", "import javax.inject.Inject;", "import javax.inject.Provider;", "", "final class B {", " @Inject B(Provider a) {}", "}"); JavaFileObject aComponentFile = JavaFileObjects.forSourceLines("test.AComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface AComponent {", " A a();", "}"); JavaFileObject bComponentFile = JavaFileObjects.forSourceLines("test.AComponent", "package test;", "", "import dagger.Component;", "", "@Component(dependencies = AComponent.class)", "interface BComponent {", " B b();", "}"); JavaFileObject generatedComponent = compilerMode .javaFileBuilder("test.DaggerBComponent") .addLines( "package test;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerBComponent implements BComponent {") .addLinesIn(DEFAULT_MODE, " private Provider aProvider;") .addLinesIn( FAST_INIT_MODE, " private final AComponent aComponent;", " private volatile Provider aProvider;", "", " private DaggerBComponent(AComponent aComponentParam) {", " this.aComponent = aComponentParam;", " }", "", " private Provider aProvider() {", " Object local = aProvider;", " if (local == null) {", " local = new SwitchingProvider<>(0);", " aProvider = (Provider) local;", " }", " return (Provider) local;", " }") .addLinesIn( DEFAULT_MODE, " @SuppressWarnings(\"unchecked\")", " private void initialize(final AComponent aComponentParam) {", " this.aProvider = new test_AComponent_a(aComponentParam);", " }") .addLines("", " @Override", " public B b() {") .addLinesIn(DEFAULT_MODE, " return new B(aProvider);") .addLinesIn(FAST_INIT_MODE, " return new B(aProvider());") .addLines( " }", "", " static final class Builder {", " private AComponent aComponent;", "", " public Builder aComponent(AComponent aComponent) {", " this.aComponent = Preconditions.checkNotNull(aComponent);", " return this;", " }", "", " public BComponent build() {", " Preconditions.checkBuilderRequirement(aComponent, AComponent.class);", " return new DaggerBComponent(aComponent);", " }", " }") .addLinesIn( DEFAULT_MODE, " private static class test_AComponent_a implements Provider {", " private final AComponent aComponent;", " ", " test_AComponent_a(AComponent aComponent) {", " this.aComponent = aComponent;", " }", " ", " @Override()", " public A get() {", " return Preconditions.checkNotNullFromComponent(aComponent.a());", " }", " }", "}") .addLinesIn( FAST_INIT_MODE, " private final class SwitchingProvider implements Provider {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", " case 0:", " return (T)", " Preconditions.checkNotNullFromComponent(", " DaggerBComponent.this.aComponent.a());", " default:", " throw new AssertionError(id);", " }", " }", " }") .build(); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(aFile, bFile, aComponentFile, bComponentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerBComponent") .containsElementsIn(generatedComponent); } @Test public void moduleNameCollision() { JavaFileObject aFile = JavaFileObjects.forSourceLines("test.A", "package test;", "", "public final class A {}"); JavaFileObject otherAFile = JavaFileObjects.forSourceLines("other.test.A", "package other.test;", "", "public final class A {}"); JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "public final class TestModule {", " @Provides A a() { return null; }", "}"); JavaFileObject otherModuleFile = JavaFileObjects.forSourceLines("other.test.TestModule", "package other.test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "public final class TestModule {", " @Provides A a() { return null; }", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", "import dagger.Component;", "import javax.inject.Provider;", "", "@Component(modules = {TestModule.class, other.test.TestModule.class})", "interface TestComponent {", " A a();", " other.test.A otherA();", "}"); JavaFileObject generatedComponent = JavaFileObjects.forSourceLines( "test.DaggerTestComponent", "package test;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerTestComponent implements TestComponent {", " private final TestModule testModule;", " private final other.test.TestModule testModule2;", "", " private DaggerTestComponent(", " TestModule testModuleParam,", " other.test.TestModule testModule2Param) {", " this.testModule = testModuleParam;", " this.testModule2 = testModule2Param;", " }", "", " @Override", " public A a() {", " return TestModule_AFactory.a(testModule);", " }", "", " @Override", " public other.test.A otherA() {", " return other.test.TestModule_AFactory.a(testModule2);", " }", "", " static final class Builder {", " private TestModule testModule;", " private other.test.TestModule testModule2;", "", " public Builder testModule(TestModule testModule) {", " this.testModule = Preconditions.checkNotNull(testModule);", " return this;", " }", "", " public Builder testModule(other.test.TestModule testModule) {", " this.testModule2 = Preconditions.checkNotNull(testModule);", " return this;", " }", "", " public TestComponent build() {", " if (testModule == null) {", " this.testModule = new TestModule();", " }", " if (testModule2 == null) {", " this.testModule2 = new other.test.TestModule();", " }", " return new DaggerTestComponent(testModule, testModule2);", " }", " }", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(aFile, otherAFile, moduleFile, otherModuleFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") .containsElementsIn(generatedComponent); } @Test public void ignoresDependencyMethodsFromObject() { JavaFileObject injectedTypeFile = JavaFileObjects.forSourceLines( "test.InjectedType", "package test;", "", "import javax.inject.Inject;", "import javax.inject.Provider;", "", "final class InjectedType {", " @Inject InjectedType(", " String stringInjection,", " int intInjection,", " AComponent aComponent,", " Class aClass) {}", "}"); JavaFileObject aComponentFile = JavaFileObjects.forSourceLines( "test.AComponent", "package test;", "", "class AComponent {", " String someStringInjection() {", " return \"injectedString\";", " }", "", " int someIntInjection() {", " return 123;", " }", "", " Class someClassInjection() {", " return AComponent.class;", " }", "", " @Override", " public String toString() {", " return null;", " }", "", " @Override", " public int hashCode() {", " return 456;", " }", "", " @Override", " public AComponent clone() {", " return null;", " }", "}"); JavaFileObject bComponentFile = JavaFileObjects.forSourceLines( "test.AComponent", "package test;", "", "import dagger.Component;", "", "@Component(dependencies = AComponent.class)", "interface BComponent {", " InjectedType injectedType();", "}"); JavaFileObject generatedComponent = JavaFileObjects.forSourceLines( "test.DaggerBComponent", "package test;", "", "import dagger.internal.Preconditions;", IMPORT_GENERATED_ANNOTATION, "", GENERATED_CODE_ANNOTATIONS, "final class DaggerBComponent implements BComponent {", " private final AComponent aComponent;", "", " private DaggerBComponent(AComponent aComponentParam) {", " this.aComponent = aComponentParam;", " }", "", " @Override", " public InjectedType injectedType() {", " return new InjectedType(", " Preconditions.checkNotNullFromComponent(", " aComponent.someStringInjection()),", " aComponent.someIntInjection(),", " aComponent,", " Preconditions.checkNotNullFromComponent(", " aComponent.someClassInjection()));", " }", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(injectedTypeFile, aComponentFile, bComponentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerBComponent") .containsElementsIn(generatedComponent); } @Test public void resolutionOrder() { JavaFileObject aFile = JavaFileObjects.forSourceLines("test.A", "package test;", "", "import javax.inject.Inject;", "", "final class A {", " @Inject A(B b) {}", "}"); JavaFileObject bFile = JavaFileObjects.forSourceLines("test.B", "package test;", "", "import javax.inject.Inject;", "", "final class B {", " @Inject B(C c) {}", "}"); JavaFileObject cFile = JavaFileObjects.forSourceLines("test.C", "package test;", "", "import javax.inject.Inject;", "", "final class C {", " @Inject C() {}", "}"); JavaFileObject xFile = JavaFileObjects.forSourceLines("test.X", "package test;", "", "import javax.inject.Inject;", "", "final class X {", " @Inject X(C c) {}", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", "import dagger.Component;", "import javax.inject.Provider;", "", "@Component", "interface TestComponent {", " A a();", " C c();", " X x();", "}"); JavaFileObject generatedComponent = compilerMode .javaFileBuilder("test.DaggerTestComponent") .addLines( "package test;", "", IMPORT_GENERATED_ANNOTATION, "", GENERATED_CODE_ANNOTATIONS, "final class DaggerTestComponent implements TestComponent {", " private B b() {", " return new B(new C());", " }", "", " @Override", " public A a() {", " return new A(b());", " }", "", " @Override", " public C c() {", " return new C();", " }", "", " @Override", " public X x() {", " return new X(new C());", " }", "}") .build(); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(aFile, bFile, cFile, xFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") .containsElementsIn(generatedComponent); } @Test public void simpleComponent_redundantComponentMethod() { JavaFileObject injectableTypeFile = JavaFileObjects.forSourceLines("test.SomeInjectableType", "package test;", "", "import javax.inject.Inject;", "", "final class SomeInjectableType {", " @Inject SomeInjectableType() {}", "}"); JavaFileObject componentSupertypeAFile = JavaFileObjects.forSourceLines("test.SupertypeA", "package test;", "", "import dagger.Component;", "import dagger.Lazy;", "import javax.inject.Provider;", "", "@Component", "interface SupertypeA {", " SomeInjectableType someInjectableType();", "}"); JavaFileObject componentSupertypeBFile = JavaFileObjects.forSourceLines("test.SupertypeB", "package test;", "", "import dagger.Component;", "import dagger.Lazy;", "import javax.inject.Provider;", "", "@Component", "interface SupertypeB {", " SomeInjectableType someInjectableType();", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent", "package test;", "", "import dagger.Component;", "import dagger.Lazy;", "import javax.inject.Provider;", "", "@Component", "interface SimpleComponent extends SupertypeA, SupertypeB {", "}"); JavaFileObject generatedComponent = JavaFileObjects.forSourceLines( "test.DaggerSimpleComponent", "package test;", "", IMPORT_GENERATED_ANNOTATION, "", GENERATED_CODE_ANNOTATIONS, "final class DaggerSimpleComponent implements SimpleComponent {", " private DaggerSimpleComponent() {}", "", " public static Builder builder() {", " return new Builder();", " }", "", " public static SimpleComponent create() {", " return new Builder().build();", " }", "", " @Override", " public SomeInjectableType someInjectableType() {", " return new SomeInjectableType();", " }", "", " static final class Builder {", " private Builder() {}", "", " public SimpleComponent build() {", " return new DaggerSimpleComponent();", " }", " }", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile( injectableTypeFile, componentSupertypeAFile, componentSupertypeBFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerSimpleComponent") .hasSourceEquivalentTo(generatedComponent); } @Test public void simpleComponent_inheritedComponentMethodDep() { JavaFileObject injectableTypeFile = JavaFileObjects.forSourceLines("test.SomeInjectableType", "package test;", "", "import javax.inject.Inject;", "", "final class SomeInjectableType {", " @Inject SomeInjectableType() {}", "}"); JavaFileObject componentSupertype = JavaFileObjects.forSourceLines("test.Supertype", "package test;", "", "import dagger.Component;", "", "@Component", "interface Supertype {", " SomeInjectableType someInjectableType();", "}"); JavaFileObject depComponentFile = JavaFileObjects.forSourceLines("test.SimpleComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface SimpleComponent extends Supertype {", "}"); JavaFileObject generatedComponent = JavaFileObjects.forSourceLines( "test.DaggerSimpleComponent", "package test;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerSimpleComponent implements SimpleComponent {", " @Override", " public SomeInjectableType someInjectableType() {", " return new SomeInjectableType();", " }", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(injectableTypeFile, componentSupertype, depComponentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerSimpleComponent") .containsElementsIn(generatedComponent); } @Test public void wildcardGenericsRequiresAtProvides() { JavaFileObject aFile = JavaFileObjects.forSourceLines("test.A", "package test;", "", "import javax.inject.Inject;", "", "final class A {", " @Inject A() {}", "}"); JavaFileObject bFile = JavaFileObjects.forSourceLines("test.B", "package test;", "", "import javax.inject.Inject;", "import javax.inject.Provider;", "", "final class B {", " @Inject B(T t) {}", "}"); JavaFileObject cFile = JavaFileObjects.forSourceLines("test.C", "package test;", "", "import javax.inject.Inject;", "import javax.inject.Provider;", "", "final class C {", " @Inject C(B bA) {}", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent", "package test;", "", "import dagger.Component;", "import dagger.Lazy;", "import javax.inject.Provider;", "", "@Component", "interface SimpleComponent {", " C c();", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(aFile, bFile, cFile, componentFile); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "test.B cannot be provided without an @Provides-annotated method"); } // https://github.com/google/dagger/issues/630 @Test public void arrayKeyRequiresAtProvides() { JavaFileObject component = JavaFileObjects.forSourceLines( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface TestComponent {", " String[] array();", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(component); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining("String[] cannot be provided without an @Provides-annotated method"); } @Test public void componentImplicitlyDependsOnGeneratedType() { JavaFileObject injectableTypeFile = JavaFileObjects.forSourceLines("test.SomeInjectableType", "package test;", "", "import javax.inject.Inject;", "", "final class SomeInjectableType {", " @Inject SomeInjectableType(GeneratedType generatedType) {}", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface SimpleComponent {", " SomeInjectableType someInjectableType();", "}"); Compilation compilation = daggerCompiler( new GeneratingProcessor( "test.GeneratedType", "package test;", "", "import javax.inject.Inject;", "", "final class GeneratedType {", " @Inject GeneratedType() {}", "}")) .withOptions(compilerMode.javacopts()) .compile(injectableTypeFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation).generatedSourceFile("test.DaggerSimpleComponent"); } @Test public void componentSupertypeDependsOnGeneratedType() { JavaFileObject componentFile = JavaFileObjects.forSourceLines( "test.SimpleComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface SimpleComponent extends SimpleComponentInterface {}"); JavaFileObject interfaceFile = JavaFileObjects.forSourceLines( "test.SimpleComponentInterface", "package test;", "", "interface SimpleComponentInterface {", " GeneratedType generatedType();", "}"); Compilation compilation = daggerCompiler( new GeneratingProcessor( "test.GeneratedType", "package test;", "", "import javax.inject.Inject;", "", "final class GeneratedType {", " @Inject GeneratedType() {}", "}")) .withOptions(compilerMode.javacopts()) .compile(componentFile, interfaceFile); assertThat(compilation).succeeded(); assertThat(compilation).generatedSourceFile("test.DaggerSimpleComponent"); } /** * We warn when generating a {@link MembersInjector} for a type post-hoc (i.e., if Dagger wasn't * invoked when compiling the type). But Dagger only generates {@link MembersInjector}s for types * with {@link Inject @Inject} constructors if they have any injection sites, and it only * generates them for types without {@link Inject @Inject} constructors if they have local * (non-inherited) injection sites. So make sure we warn in only those cases where running the * Dagger processor actually generates a {@link MembersInjector}. */ @Test public void unprocessedMembersInjectorNotes() { Compilation compilation = javac() .withOptions( compilerMode .javacopts() .append( "-Xlint:-processing", "-Adagger.warnIfInjectionFactoryNotGeneratedUpstream=enabled")) .withProcessors( new ElementFilteringComponentProcessor( Predicates.not( element -> MoreElements.getPackage(element) .getQualifiedName() .contentEquals("test.inject")))) .compile( JavaFileObjects.forSourceLines( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = TestModule.class)", "interface TestComponent {", " void inject(test.inject.NoInjectMemberNoConstructor object);", " void inject(test.inject.NoInjectMemberWithConstructor object);", " void inject(test.inject.LocalInjectMemberNoConstructor object);", " void inject(test.inject.LocalInjectMemberWithConstructor object);", " void inject(test.inject.ParentInjectMemberNoConstructor object);", " void inject(test.inject.ParentInjectMemberWithConstructor object);", "}"), JavaFileObjects.forSourceLines( "test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "class TestModule {", " @Provides static Object object() {", " return \"object\";", " }", "}"), JavaFileObjects.forSourceLines( "test.inject.NoInjectMemberNoConstructor", "package test.inject;", "", "public class NoInjectMemberNoConstructor {", "}"), JavaFileObjects.forSourceLines( "test.inject.NoInjectMemberWithConstructor", "package test.inject;", "", "import javax.inject.Inject;", "", "public class NoInjectMemberWithConstructor {", " @Inject NoInjectMemberWithConstructor() {}", "}"), JavaFileObjects.forSourceLines( "test.inject.LocalInjectMemberNoConstructor", "package test.inject;", "", "import javax.inject.Inject;", "", "public class LocalInjectMemberNoConstructor {", " @Inject Object object;", "}"), JavaFileObjects.forSourceLines( "test.inject.LocalInjectMemberWithConstructor", "package test.inject;", "", "import javax.inject.Inject;", "", "public class LocalInjectMemberWithConstructor {", " @Inject LocalInjectMemberWithConstructor() {}", " @Inject Object object;", "}"), JavaFileObjects.forSourceLines( "test.inject.ParentInjectMemberNoConstructor", "package test.inject;", "", "import javax.inject.Inject;", "", "public class ParentInjectMemberNoConstructor", " extends LocalInjectMemberNoConstructor {}"), JavaFileObjects.forSourceLines( "test.inject.ParentInjectMemberWithConstructor", "package test.inject;", "", "import javax.inject.Inject;", "", "public class ParentInjectMemberWithConstructor", " extends LocalInjectMemberNoConstructor {", " @Inject ParentInjectMemberWithConstructor() {}", "}")); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) .hadNoteContaining( "Generating a MembersInjector for " + "test.inject.LocalInjectMemberNoConstructor. " + "Prefer to run the dagger processor over that class instead."); assertThat(compilation) .hadNoteContaining( "Generating a MembersInjector for " + "test.inject.LocalInjectMemberWithConstructor. " + "Prefer to run the dagger processor over that class instead."); assertThat(compilation) .hadNoteContaining( "Generating a MembersInjector for " + "test.inject.ParentInjectMemberWithConstructor. " + "Prefer to run the dagger processor over that class instead."); assertThat(compilation).hadNoteCount(3); } @Test public void scopeAnnotationOnInjectConstructorNotValid() { JavaFileObject aScope = JavaFileObjects.forSourceLines( "test.AScope", "package test;", "", "import javax.inject.Scope;", "", "@Scope", "@interface AScope {}"); JavaFileObject aClass = JavaFileObjects.forSourceLines( "test.AClass", "package test;", "", "import javax.inject.Inject;", "", "final class AClass {", " @Inject @AScope AClass() {}", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(aScope, aClass); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining("@Scope annotations are not allowed on @Inject constructors") .inFile(aClass) .onLine(6); } @Test public void unusedSubcomponents_dontResolveExtraBindingsInParentComponents() { JavaFileObject foo = JavaFileObjects.forSourceLines( "test.Foo", "package test;", "", "import javax.inject.Inject;", "import javax.inject.Singleton;", "", "@Singleton", "class Foo {", " @Inject Foo() {}", "}"); JavaFileObject module = JavaFileObjects.forSourceLines( "test.TestModule", "package test;", "", "import dagger.Module;", "", "@Module(subcomponents = Pruned.class)", "class TestModule {}"); JavaFileObject component = JavaFileObjects.forSourceLines( "test.Parent", "package test;", "", "import dagger.Component;", "import javax.inject.Singleton;", "", "@Singleton", "@Component(modules = TestModule.class)", "interface Parent {}"); JavaFileObject prunedSubcomponent = JavaFileObjects.forSourceLines( "test.Pruned", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent", "interface Pruned {", " @Subcomponent.Builder", " interface Builder {", " Pruned build();", " }", "", " Foo foo();", "}"); JavaFileObject generated = JavaFileObjects.forSourceLines( "test.DaggerParent", "package test;", "", "import dagger.internal.Preconditions;", IMPORT_GENERATED_ANNOTATION, "", GENERATED_CODE_ANNOTATIONS, "final class DaggerParent implements Parent {", " private DaggerParent() {", " }", "", " public static Builder builder() {", " return new Builder();", " }", "", " public static Parent create() {", " return new Builder().build();", " }", "", " static final class Builder {", " private Builder() {}", "", " @Deprecated", " public Builder testModule(TestModule testModule) {", " Preconditions.checkNotNull(testModule);", " return this;", " }", "", " public Parent build() {", " return new DaggerParent();", " }", " }", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(foo, module, component, prunedSubcomponent); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerParent") .hasSourceEquivalentTo(generated); } @Test public void bindsToDuplicateBinding_bindsKeyIsNotDuplicated() { JavaFileObject firstModule = JavaFileObjects.forSourceLines( "test.FirstModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "abstract class FirstModule {", " @Provides static String first() { return \"first\"; }", "}"); JavaFileObject secondModule = JavaFileObjects.forSourceLines( "test.SecondModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "abstract class SecondModule {", " @Provides static String second() { return \"second\"; }", "}"); JavaFileObject bindsModule = JavaFileObjects.forSourceLines( "test.BindsModule", "package test;", "", "import dagger.Binds;", "import dagger.Module;", "", "@Module", "abstract class BindsModule {", " @Binds abstract Object bindToDuplicateBinding(String duplicate);", "}"); JavaFileObject component = JavaFileObjects.forSourceLines( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = {FirstModule.class, SecondModule.class, BindsModule.class})", "interface TestComponent {", " Object notDuplicated();", "}"); Compilation compilation = daggerCompiler().compile(firstModule, secondModule, bindsModule, component); assertThat(compilation).failed(); assertThat(compilation).hadErrorCount(1); assertThat(compilation) .hadErrorContaining("String is bound multiple times") .inFile(component) .onLineContaining("interface TestComponent"); } @Test public void nullIncorrectlyReturnedFromNonNullableInlinedProvider() { Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile( JavaFileObjects.forSourceLines( "test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "public abstract class TestModule {", " @Provides static String nonNullableString() { return \"string\"; }", "}"), JavaFileObjects.forSourceLines( "test.InjectsMember", "package test;", "", "import javax.inject.Inject;", "", "public class InjectsMember {", " @Inject String member;", "}"), JavaFileObjects.forSourceLines( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = TestModule.class)", "interface TestComponent {", " String nonNullableString();", " void inject(InjectsMember member);", "}")); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) .generatedSourceFile("test.TestModule_NonNullableStringFactory") .containsElementsIn( JavaFileObjects.forSourceLines( "test.TestModule_NonNullableStringFactory", "package test;", "", GENERATED_CODE_ANNOTATIONS, "public final class TestModule_NonNullableStringFactory", " implements Factory {", " @Override", " public String get() {", " return nonNullableString();", " }", "", " public static String nonNullableString() {", " return Preconditions.checkNotNullFromProvides(", " TestModule.nonNullableString());", " }", "}")); JavaFileObject generatedComponent = compilerMode .javaFileBuilder("test.DaggerTestComponent") .addLines( "package test;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerTestComponent implements TestComponent {", " @Override", " public String nonNullableString() {", " return TestModule_NonNullableStringFactory.nonNullableString());", " }", "", " @Override", " public void inject(InjectsMember member) {", " injectInjectsMember(member);", " }", "", " @CanIgnoreReturnValue", " private InjectsMember injectInjectsMember(InjectsMember instance) {", " InjectsMember_MembersInjector.injectMember(instance,", " TestModule_NonNullableStringFactory.nonNullableString());", " return instance;", " }", "}") .build(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") .containsElementsIn(generatedComponent); } @Test public void nullCheckingIgnoredWhenProviderReturnsPrimitive() { Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile( JavaFileObjects.forSourceLines( "test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "public abstract class TestModule {", " @Provides static int primitiveInteger() { return 1; }", "}"), JavaFileObjects.forSourceLines( "test.InjectsMember", "package test;", "", "import javax.inject.Inject;", "", "public class InjectsMember {", " @Inject Integer member;", "}"), JavaFileObjects.forSourceLines( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = TestModule.class)", "interface TestComponent {", " Integer nonNullableInteger();", " void inject(InjectsMember member);", "}")); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) .generatedSourceFile("test.TestModule_PrimitiveIntegerFactory") .containsElementsIn( JavaFileObjects.forSourceLines( "test.TestModule_PrimitiveIntegerFactory", "package test;", "", GENERATED_CODE_ANNOTATIONS, "public final class TestModule_PrimitiveIntegerFactory", " implements Factory {", "", " @Override", " public Integer get() {", " return primitiveInteger();", " }", "", " public static int primitiveInteger() {", " return TestModule.primitiveInteger();", " }", "}")); JavaFileObject generatedComponent = compilerMode .javaFileBuilder("test.DaggerTestComponent") .addLines( "package test;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerTestComponent implements TestComponent {", " @Override", " public Integer nonNullableInteger() {", " return TestModule.primitiveInteger();", " }", "", " @Override", " public void inject(InjectsMember member) {", " injectInjectsMember(member);", " }", "", " @CanIgnoreReturnValue", " private InjectsMember injectInjectsMember(InjectsMember instance) {", " InjectsMember_MembersInjector.injectMember(", " instance, TestModule.primitiveInteger());", " return instance;", " }", "}") .build(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") .containsElementsIn(generatedComponent); } @Test public void privateMethodUsedOnlyInChildDoesNotUseQualifiedThis() { JavaFileObject parent = JavaFileObjects.forSourceLines( "test.Parent", "package test;", "", "import dagger.Component;", "import javax.inject.Singleton;", "", "@Singleton", "@Component(modules=TestModule.class)", "interface Parent {", " Child child();", "}"); JavaFileObject testModule = JavaFileObjects.forSourceLines( "test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import javax.inject.Singleton;", "", "@Module", "abstract class TestModule {", " @Provides @Singleton static Number number() {", " return 3;", " }", "", " @Provides static String string(Number number) {", " return number.toString();", " }", "}"); JavaFileObject child = JavaFileObjects.forSourceLines( "test.Child", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent", "interface Child {", " String string();", "}"); JavaFileObject expectedPattern = JavaFileObjects.forSourceLines( "test.DaggerParent", "package test;", GENERATED_CODE_ANNOTATIONS, "final class DaggerParent implements Parent {", " private String string() {", " return TestModule_StringFactory.string(numberProvider.get());", " }", "}"); Compilation compilation = daggerCompiler().compile(parent, testModule, child); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) .generatedSourceFile("test.DaggerParent") .containsElementsIn(expectedPattern); } @Test public void componentMethodInChildCallsComponentMethodInParent() { JavaFileObject supertype = JavaFileObjects.forSourceLines( "test.Supertype", "package test;", "", "interface Supertype {", " String string();", "}"); JavaFileObject parent = JavaFileObjects.forSourceLines( "test.Parent", "package test;", "", "import dagger.Component;", "import javax.inject.Singleton;", "", "@Singleton", "@Component(modules=TestModule.class)", "interface Parent extends Supertype {", " Child child();", "}"); JavaFileObject testModule = JavaFileObjects.forSourceLines( "test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import javax.inject.Singleton;", "", "@Module", "abstract class TestModule {", " @Provides @Singleton static Number number() {", " return 3;", " }", "", " @Provides static String string(Number number) {", " return number.toString();", " }", "}"); JavaFileObject child = JavaFileObjects.forSourceLines( "test.Child", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent", "interface Child extends Supertype {}"); JavaFileObject expectedPattern = JavaFileObjects.forSourceLines( "test.DaggerParent", "package test;", GENERATED_CODE_ANNOTATIONS, "final class DaggerParent implements Parent {", " private final class ChildImpl implements Child {", " @Override", " public String string() {", " return DaggerParent.this.string();", " }", " }", "}"); Compilation compilation = daggerCompiler().compile(supertype, parent, testModule, child); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) .generatedSourceFile("test.DaggerParent") .containsElementsIn(expectedPattern); } @Test public void justInTimeAtInjectConstructor_hasGeneratedQualifier() { JavaFileObject injected = JavaFileObjects.forSourceLines( "test.Injected", "package test;", "", "import javax.inject.Inject;", "", "class Injected {", " @Inject Injected(@GeneratedQualifier String string) {}", "}"); JavaFileObject module = JavaFileObjects.forSourceLines( "test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "interface TestModule {", " @Provides", " static String unqualified() {", " return new String();", " }", "", " @Provides", " @GeneratedQualifier", " static String qualified() {", " return new String();", " }", "}"); JavaFileObject component = JavaFileObjects.forSourceLines( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = TestModule.class)", "interface TestComponent {", " Injected injected();", "}"); JavaFileObject generatedComponent = JavaFileObjects.forSourceLines( "test.DaggerTestComponent", "package test;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerTestComponent implements TestComponent {", " @Override", " public Injected injected() {", // Ensure that the qualified @Provides method is used. It's also probably more likely // that if the qualifier type hasn't been generated, a duplicate binding error will be // reported, since the annotation won't be recognized as a qualifier and instead as an // ordinary annotation. " return new Injected(TestModule_QualifiedFactory.qualified());", " }", "}"); Compilation compilation = daggerCompiler( new GeneratingProcessor( "test.GeneratedQualifier", "package test;", "", "import static java.lang.annotation.RetentionPolicy.RUNTIME;", "", "import java.lang.annotation.Retention;", "import javax.inject.Qualifier;", "", "@Retention(RUNTIME)", "@Qualifier", "@interface GeneratedQualifier {}")) .compile(injected, module, component); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") .containsElementsIn(generatedComponent); } @Test public void moduleHasGeneratedQualifier() { JavaFileObject module = JavaFileObjects.forSourceLines( "test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "interface TestModule {", " @Provides", " static String unqualified() {", " return new String();", " }", "", " @Provides", " @GeneratedQualifier", " static String qualified() {", " return new String();", " }", "}"); JavaFileObject component = JavaFileObjects.forSourceLines( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = TestModule.class)", "interface TestComponent {", " String unqualified();", "}"); JavaFileObject generatedComponent = JavaFileObjects.forSourceLines( "test.DaggerTestComponent", "package test;", "", GENERATED_CODE_ANNOTATIONS, "final class DaggerTestComponent implements TestComponent {", " @Override", " public String unqualified() {", // Ensure that the unqualified @Provides method is used. It's also probably more likely // if the qualifier hasn't been generated, a duplicate binding exception will be thrown // since the annotation won't be considered a qualifier " return TestModule_UnqualifiedFactory.unqualified();", " }", "}"); Compilation compilation = daggerCompiler( new GeneratingProcessor( "test.GeneratedQualifier", "package test;", "", "import static java.lang.annotation.RetentionPolicy.RUNTIME;", "", "import java.lang.annotation.Retention;", "import javax.inject.Qualifier;", "", "@Retention(RUNTIME)", "@Qualifier", "@interface GeneratedQualifier {}")) .compile(module, component); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") .containsElementsIn(generatedComponent); } @Test public void publicComponentType() { JavaFileObject publicComponent = JavaFileObjects.forSourceLines( "test.PublicComponent", "package test;", "", "import dagger.Component;", "", "@Component", "public interface PublicComponent {}"); Compilation compilation = daggerCompiler().compile(publicComponent); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerPublicComponent") .hasSourceEquivalentTo( JavaFileObjects.forSourceLines( "test.DaggerPublicComponent", "package test;", "", IMPORT_GENERATED_ANNOTATION, "", GENERATED_CODE_ANNOTATIONS, "public final class DaggerPublicComponent implements PublicComponent {", " private DaggerPublicComponent() {}", "", " public static Builder builder() {", " return new Builder();", " }", "", " public static PublicComponent create() {", " return new Builder().build();", " }", "", " public static final class Builder {", " private Builder() {}", "", " public PublicComponent build() {", " return new DaggerPublicComponent();", " }", " }", "}")); } /** * A {@link ComponentProcessor} that excludes elements using a {@link Predicate}. */ private static final class ElementFilteringComponentProcessor extends AbstractProcessor { private final ComponentProcessor componentProcessor = new ComponentProcessor(); private final Predicate filter; /** * Creates a {@link ComponentProcessor} that only processes elements that match {@code filter}. */ public ElementFilteringComponentProcessor(Predicate filter) { this.filter = filter; } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); componentProcessor.init(processingEnv); } @Override public Set getSupportedAnnotationTypes() { return componentProcessor.getSupportedAnnotationTypes(); } @Override public SourceVersion getSupportedSourceVersion() { return componentProcessor.getSupportedSourceVersion(); } @Override public Set getSupportedOptions() { return componentProcessor.getSupportedOptions(); } @Override public boolean process( Set annotations, final RoundEnvironment roundEnv) { return componentProcessor.process( annotations, new RoundEnvironment() { @Override public boolean processingOver() { return roundEnv.processingOver(); } @Override public Set getRootElements() { return Sets.filter(roundEnv.getRootElements(), filter); } @Override public Set getElementsAnnotatedWith(Class a) { return Sets.filter(roundEnv.getElementsAnnotatedWith(a), filter); } @Override public Set getElementsAnnotatedWith(TypeElement a) { return Sets.filter(roundEnv.getElementsAnnotatedWith(a), filter); } @Override public boolean errorRaised() { return roundEnv.errorRaised(); } }); } } }