diff --git a/compiler/src/it/functional-tests/src/main/java/test/ModuleIncludesCollectedFromModuleSuperclasses.java b/compiler/src/it/functional-tests/src/main/java/test/ModuleIncludesCollectedFromModuleSuperclasses.java new file mode 100644 index 00000000000..0b330a1f905 --- /dev/null +++ b/compiler/src/it/functional-tests/src/main/java/test/ModuleIncludesCollectedFromModuleSuperclasses.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 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 test; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; + +/** + * This tests that @Module.includes are traversed for supertypes of a module. + */ +final class ModuleIncludesCollectedFromModuleSuperclasses { + @Component(modules = TopLevelModule.class) + interface C { + Foo foo(); + int includedInTopLevelModule(); + String includedFromModuleInheritance(); + } + + @Module(includes = IncludedTopLevel.class) + static class TopLevelModule extends FooModule {} + + static class Foo {} + + @Module(includes = IncludedFromModuleInheritance.class) + abstract static class FooModule extends FooCreator { + @Provides Foo fooOfT() { + return createFoo(); + } + } + + static class FooCreator { + Foo createFoo() { + return new Foo(); + } + } + + @Module + static class IncludedTopLevel { + @Provides int i() { + return 123; + } + } + + @Module + static class IncludedFromModuleInheritance { + @Provides String inheritedProvision() { + return "inherited"; + } + } +} diff --git a/compiler/src/it/functional-tests/src/main/java/test/subcomponent/SubcomponentFromModuleAndFactoryMethod.java b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/SubcomponentFromModuleAndFactoryMethod.java new file mode 100644 index 00000000000..8a04944ab94 --- /dev/null +++ b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/SubcomponentFromModuleAndFactoryMethod.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 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 test.subcomponent; + +import dagger.Component; +import dagger.Module; +import dagger.Subcomponent; + +/** + * Tests for {@link Subcomponent}s which are defined with {@link Module#subcomponents()} and are + * also requested as component factory methods. + */ +public class SubcomponentFromModuleAndFactoryMethod { + @Subcomponent + interface Sub { + @Subcomponent.Builder + interface Builder { + Sub sub(); + } + } + + @Module(subcomponents = Sub.class) + class ModuleWithSubcomponent {} + + @Component(modules = ModuleWithSubcomponent.class) + interface ExposesBuilder { + Sub.Builder subcomponentBuilder(); + } +} diff --git a/compiler/src/it/functional-tests/src/main/java/test/subcomponent/UsesModuleSubcomponents.java b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/UsesModuleSubcomponents.java new file mode 100644 index 00000000000..9c7ba31115a --- /dev/null +++ b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/UsesModuleSubcomponents.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 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 test.subcomponent; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; +import dagger.multibindings.IntoSet; +import java.util.Set; +import javax.inject.Inject; + +/** Supporting types for {@link ModuleWithSubcomponentsTest}. */ +@Component(modules = UsesModuleSubcomponents.ModuleWithSubcomponents.class) +public interface UsesModuleSubcomponents { + UsesChild usesChild(); + + Set strings(); + + @Module(subcomponents = Child.class, includes = AlsoIncludesSubcomponents.class) + class ModuleWithSubcomponents { + @Provides + @IntoSet + static String provideStringInParent() { + return "from parent"; + } + } + + @Module(subcomponents = Child.class) + class AlsoIncludesSubcomponents {} + + @Subcomponent(modules = ChildModule.class) + interface Child { + Set strings(); + + @Subcomponent.Builder + interface Builder { + Child build(); + } + } + + @Module + class ChildModule { + @Provides + @IntoSet + static String provideStringInChild() { + return "from child"; + } + } + + class UsesChild { + Set strings; + + @Inject + UsesChild(Child.Builder childBuilder) { + this.strings = childBuilder.build().strings(); + } + } + + @Module(includes = ModuleWithSubcomponents.class) + class OnlyIncludesModuleWithSubcomponents {} + + @Component(modules = OnlyIncludesModuleWithSubcomponents.class) + interface ParentIncludesSubcomponentTransitively extends UsesModuleSubcomponents {} + +} diff --git a/compiler/src/it/functional-tests/src/main/java/test/subcomponent/pruning/ParentDoesntUseSubcomponent.java b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/pruning/ParentDoesntUseSubcomponent.java new file mode 100644 index 00000000000..3126f13f368 --- /dev/null +++ b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/pruning/ParentDoesntUseSubcomponent.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 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 test.subcomponent.pruning; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; +import dagger.multibindings.IntoSet; +import java.util.Set; +import javax.inject.Qualifier; + +/** + * Supporting types for {@link SubcomponentOnlyRequestedBySiblingTest}. {@link ChildA} is a direct + * child of the top level component, but is only requested within its sibling, not directly from its + * parent. + */ +@Component(modules = ParentDoesntUseSubcomponent.ParentModule.class) +interface ParentDoesntUseSubcomponent { + + ChildB.Builder childBBuilder(); + + @Subcomponent(modules = ChildAModule.class) + interface ChildA { + @Subcomponent.Builder + interface Builder { + ChildA build(); + } + + Set> componentHierarchy(); + } + + @Subcomponent(modules = ChildBModule.class) + interface ChildB { + @Subcomponent.Builder + interface Builder { + ChildB build(); + } + + Set> componentHierarchy(); + + @FromChildA + Set> componentHierarchyFromChildA(); + } + + @Module(subcomponents = {ChildA.class, ChildB.class}) + class ParentModule { + @Provides + @IntoSet + static Class provideComponentType() { + return ParentDoesntUseSubcomponent.class; + } + } + + @Module + class ChildAModule { + @Provides + @IntoSet + static Class provideComponentType() { + return ChildA.class; + } + } + + @Module + class ChildBModule { + @Provides + @IntoSet + static Class provideComponentType() { + return ChildB.class; + } + + @Provides + @FromChildA + Set> fromChildA(ChildA.Builder childABuilder) { + return childABuilder.build().componentHierarchy(); + } + } + + @Qualifier + @interface FromChildA {} +} diff --git a/compiler/src/it/functional-tests/src/main/java/test/subcomponent/repeat/OtherSubcomponentWithRepeatedModule.java b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/repeat/OtherSubcomponentWithRepeatedModule.java new file mode 100644 index 00000000000..31844a5ed01 --- /dev/null +++ b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/repeat/OtherSubcomponentWithRepeatedModule.java @@ -0,0 +1,30 @@ +/* + * 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 test.subcomponent.repeat; + +import dagger.Subcomponent; + +@Subcomponent(modules = RepeatedModule.class) +interface OtherSubcomponentWithRepeatedModule extends SubcomponentWithRepeatedModule { + + @Subcomponent.Builder + interface Builder { + Builder repeatedModule(RepeatedModule repeatedModule); + + OtherSubcomponentWithRepeatedModule build(); + } +} diff --git a/compiler/src/it/functional-tests/src/main/java/test/subcomponent/repeat/SubcomponentWithoutRepeatedModule.java b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/repeat/SubcomponentWithoutRepeatedModule.java index 2b6950462f7..e7829a02503 100644 --- a/compiler/src/it/functional-tests/src/main/java/test/subcomponent/repeat/SubcomponentWithoutRepeatedModule.java +++ b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/repeat/SubcomponentWithoutRepeatedModule.java @@ -20,5 +20,5 @@ @Subcomponent interface SubcomponentWithoutRepeatedModule { - SubcomponentWithRepeatedModule.Builder newGrandchildBuilder(); + OtherSubcomponentWithRepeatedModule.Builder newGrandchildBuilder(); } diff --git a/compiler/src/it/functional-tests/src/test/java/test/subcomponent/ModuleWithSubcomponentsTest.java b/compiler/src/it/functional-tests/src/test/java/test/subcomponent/ModuleWithSubcomponentsTest.java new file mode 100644 index 00000000000..34dbf8825f1 --- /dev/null +++ b/compiler/src/it/functional-tests/src/test/java/test/subcomponent/ModuleWithSubcomponentsTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016 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 test.subcomponent; + +import static com.google.common.truth.Truth.assertThat; + +import dagger.Module; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import test.subcomponent.UsesModuleSubcomponents.ParentIncludesSubcomponentTransitively; + +/** Tests for {@link Module#subcomponents()}. */ +@RunWith(JUnit4.class) +public class ModuleWithSubcomponentsTest { + + @Test + public void subcomponentFromModules() { + UsesModuleSubcomponents parent = DaggerUsesModuleSubcomponents.create(); + assertThat(parent.strings()).containsExactly("from parent"); + assertThat(parent.usesChild().strings).containsExactly("from parent", "from child"); + } + + @Test + public void subcomponentFromModules_transitively() { + ParentIncludesSubcomponentTransitively parent = + DaggerUsesModuleSubcomponents_ParentIncludesSubcomponentTransitively.create(); + assertThat(parent.strings()).containsExactly("from parent"); + assertThat(parent.usesChild().strings).containsExactly("from parent", "from child"); + } +} diff --git a/compiler/src/it/functional-tests/src/test/java/test/subcomponent/pruning/SubcomponentOnlyRequestedBySiblingTest.java b/compiler/src/it/functional-tests/src/test/java/test/subcomponent/pruning/SubcomponentOnlyRequestedBySiblingTest.java new file mode 100644 index 00000000000..fb856a73d41 --- /dev/null +++ b/compiler/src/it/functional-tests/src/test/java/test/subcomponent/pruning/SubcomponentOnlyRequestedBySiblingTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 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 test.subcomponent.pruning; + +import static com.google.common.truth.Truth.assertThat; + +import dagger.Module; +import dagger.Subcomponent; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import test.subcomponent.pruning.ParentDoesntUseSubcomponent.ChildA; +import test.subcomponent.pruning.ParentDoesntUseSubcomponent.ChildB; + +/** + * Tests for {@link Subcomponent}s which are included with {@link Module#subcomponents()} but not + * used directly within the component which adds them. + * + *

This tests to make sure that while resolving one subcomponent (A), another subcomponent (B) + * can be requested if they have a shared ancestor component. If that shared ancestor did not + * resolve B directly via any of its entry points, B will still be generated since it is requested + * by a descendant. + */ +@RunWith(JUnit4.class) +public class SubcomponentOnlyRequestedBySiblingTest { + @Test + public void subcomponentAddedInParent_onlyUsedInSibling() { + ParentDoesntUseSubcomponent parent = DaggerParentDoesntUseSubcomponent.create(); + ChildB childB = parent.childBBuilder().build(); + assertThat(childB.componentHierarchy()) + .containsExactly(ParentDoesntUseSubcomponent.class, ChildB.class); + assertThat(childB.componentHierarchyFromChildA()) + .containsExactly(ParentDoesntUseSubcomponent.class, ChildA.class); + } +} diff --git a/compiler/src/it/functional-tests/src/test/java/test/subcomponent/repeat/RepeatedModuleTest.java b/compiler/src/it/functional-tests/src/test/java/test/subcomponent/repeat/RepeatedModuleTest.java index de8de4b97e7..e4b21c1bac9 100644 --- a/compiler/src/it/functional-tests/src/test/java/test/subcomponent/repeat/RepeatedModuleTest.java +++ b/compiler/src/it/functional-tests/src/test/java/test/subcomponent/repeat/RepeatedModuleTest.java @@ -68,7 +68,7 @@ public void repeatedModuleBuilderThrowsInSubcomponent() { public void repeatedModuleBuilderThrowsInGrandchildSubcomponent() { SubcomponentWithoutRepeatedModule childComponent = parentComponent.newChildComponentWithoutRepeatedModule(); - SubcomponentWithRepeatedModule.Builder grandchildComponentBuilder = + OtherSubcomponentWithRepeatedModule.Builder grandchildComponentBuilder = childComponent.newGrandchildBuilder(); try { grandchildComponentBuilder.repeatedModule(new RepeatedModule()); diff --git a/compiler/src/it/guava-functional-tests/pom.xml b/compiler/src/it/guava-functional-tests/pom.xml new file mode 100644 index 00000000000..cb127fa0eff --- /dev/null +++ b/compiler/src/it/guava-functional-tests/pom.xml @@ -0,0 +1,78 @@ + + + + 4.0.0 + + com.google.dagger + dagger-parent + HEAD-SNAPSHOT + + dagger.tests + guava-functional-tests + Guava Functional Tests + + + com.google.dagger + dagger + ${project.version} + + + com.google.dagger + dagger-compiler + ${project.version} + true + + + com.google.auto.value + auto-value + ${auto.value.version} + provided + + + com.google.auto.factory + auto-factory + ${auto.factory.version} + provided + + + + junit + junit + test + + + com.google.truth + truth + test + + + + + + maven-compiler-plugin + 3.1 + + 1.7 + 1.7 + + + + + diff --git a/compiler/src/it/guava-functional-tests/src/main/java/test/optional/OptionalBindingComponents.java b/compiler/src/it/guava-functional-tests/src/main/java/test/optional/OptionalBindingComponents.java new file mode 100644 index 00000000000..c57b0cab3ad --- /dev/null +++ b/compiler/src/it/guava-functional-tests/src/main/java/test/optional/OptionalBindingComponents.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2016 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 test.optional; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; +import dagger.BindsOptionalOf; +import dagger.Component; +import dagger.Lazy; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; +import java.lang.annotation.Retention; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Qualifier; + +/** + * Classes to support {@link OptionalBindingComponentsTest} and + * {@link test.optional.a.OptionalBindingComponentsWithInaccessibleTypesTest}. + */ +public final class OptionalBindingComponents { + + /** A qualifier. */ + @Qualifier + @Retention(RUNTIME) + public @interface SomeQualifier {} + + /** A value object that contains various optionally-bound objects. */ + @AutoValue + public abstract static class Values { + abstract Optional optionalInstance(); + + abstract Optional> optionalProvider(); + + abstract Optional> optionalLazy(); + + abstract Optional>> optionalLazyProvider(); + } + + // Default access so that it's inaccessible to OptionalBindingComponentsWithInaccessibleTypes. + enum Value { + VALUE, + QUALIFIED_VALUE + } + + static final class InjectedThing { + @Inject + InjectedThing() {} + } + + /** Binds optionals and {@link Values}. */ + @Module + public abstract static class OptionalBindingModule { + @BindsOptionalOf + abstract Value value(); + + @BindsOptionalOf + @SomeQualifier abstract Value qualifiedValue(); + + // Valid because it's qualified. + @BindsOptionalOf + @SomeQualifier abstract InjectedThing qualifiedInjectedThing(); + + @Provides + static Values values( + Optional optionalInstance, + Optional> optionalProvider, + Optional> optionalLazy, + Optional>> optionalLazyProvider) { + return new AutoValue_OptionalBindingComponents_Values( + optionalInstance, optionalProvider, optionalLazy, optionalLazyProvider); + } + + @Provides + @SomeQualifier + static Values qualifiedValues( + @SomeQualifier Optional optionalInstance, + @SomeQualifier Optional> optionalProvider, + @SomeQualifier Optional> optionalLazy, + @SomeQualifier Optional>> optionalLazyProvider) { + return new AutoValue_OptionalBindingComponents_Values( + optionalInstance, optionalProvider, optionalLazy, optionalLazyProvider); + } + } + + /** Binds {@link Value}. */ + @Module + public abstract static class ConcreteBindingModule { + /** @param cycle to demonstrate that optional {@link Provider} injection can break cycles */ + @Provides + static Value value(Optional> cycle) { + return Value.VALUE; + } + + @Provides + @SomeQualifier static Value qualifiedValue() { + return Value.QUALIFIED_VALUE; + } + } + + interface OptionalBindingComponent { + Values values(); + + Optional optionalInstance(); + + Optional> optionalProvider(); + + Optional> optionalLazy(); + + Optional>> optionalLazyProvider(); + + @SomeQualifier + Values qualifiedValues(); + + @SomeQualifier + Optional qualifiedOptionalInstance(); + + @SomeQualifier + Optional> qualifiedOptionalProvider(); + + @SomeQualifier + Optional> qualifiedOptionalLazy(); + + @SomeQualifier + Optional>> qualifiedOptionalLazyProvider(); + } + + @Component(modules = OptionalBindingModule.class) + interface AbsentOptionalBindingComponent extends OptionalBindingComponent { + PresentOptionalBindingSubcomponent presentChild(); + } + + @Component(modules = {OptionalBindingModule.class, ConcreteBindingModule.class}) + interface PresentOptionalBindingComponent extends OptionalBindingComponent {} + + @Subcomponent(modules = ConcreteBindingModule.class) + interface PresentOptionalBindingSubcomponent extends OptionalBindingComponent {} +} diff --git a/compiler/src/it/guava-functional-tests/src/main/java/test/optional/a/OptionalBindingComponentsWithInaccessibleTypes.java b/compiler/src/it/guava-functional-tests/src/main/java/test/optional/a/OptionalBindingComponentsWithInaccessibleTypes.java new file mode 100644 index 00000000000..f9a2f758c09 --- /dev/null +++ b/compiler/src/it/guava-functional-tests/src/main/java/test/optional/a/OptionalBindingComponentsWithInaccessibleTypes.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 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 test.optional.a; + +import dagger.Component; +import test.optional.OptionalBindingComponents.ConcreteBindingModule; +import test.optional.OptionalBindingComponents.OptionalBindingModule; +import test.optional.OptionalBindingComponents.SomeQualifier; +import test.optional.OptionalBindingComponents.Values; + +final class OptionalBindingComponentsWithInaccessibleTypes { + + interface OptionalBindingComponent { + Values values(); + + @SomeQualifier + Values qualifiedValues(); + } + + @Component(modules = OptionalBindingModule.class) + interface AbsentOptionalBindingComponent extends OptionalBindingComponent {} + + @Component(modules = {OptionalBindingModule.class, ConcreteBindingModule.class}) + interface PresentOptionalBindingComponent extends OptionalBindingComponent {} +} diff --git a/compiler/src/it/guava-functional-tests/src/test/java/test/optional/OptionalBindingComponentsTest.java b/compiler/src/it/guava-functional-tests/src/test/java/test/optional/OptionalBindingComponentsTest.java new file mode 100644 index 00000000000..ef437cf849a --- /dev/null +++ b/compiler/src/it/guava-functional-tests/src/test/java/test/optional/OptionalBindingComponentsTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2016 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 test.optional; + +import static com.google.common.truth.Truth.assertThat; +import static test.optional.OptionalBindingComponents.Value.QUALIFIED_VALUE; +import static test.optional.OptionalBindingComponents.Value.VALUE; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import test.optional.OptionalBindingComponents.AbsentOptionalBindingComponent; +import test.optional.OptionalBindingComponents.PresentOptionalBindingComponent; +import test.optional.OptionalBindingComponents.PresentOptionalBindingSubcomponent; + +/** Tests for optional bindings. */ +@RunWith(JUnit4.class) +public final class OptionalBindingComponentsTest { + private AbsentOptionalBindingComponent absent; + private PresentOptionalBindingComponent present; + private PresentOptionalBindingSubcomponent presentChild; + + @Before + public void setUp() { + absent = DaggerOptionalBindingComponents_AbsentOptionalBindingComponent.create(); + present = DaggerOptionalBindingComponents_PresentOptionalBindingComponent.create(); + presentChild = absent.presentChild(); + } + + @Test + public void absentOptional() { + assertThat(absent.optionalInstance()).isAbsent(); + } + + @Test + public void absentOptionalProvider() { + assertThat(absent.optionalProvider()).isAbsent(); + } + + @Test + public void absentOptionalLazy() { + assertThat(absent.optionalLazy()).isAbsent(); + } + + @Test + public void absentOptionalLazyProvider() { + assertThat(absent.optionalLazyProvider()).isAbsent(); + } + + @Test + public void absentQualifiedOptional() { + assertThat(absent.qualifiedOptionalInstance()).isAbsent(); + } + + @Test + public void absentQualifiedOptionalProvider() { + assertThat(absent.qualifiedOptionalProvider()).isAbsent(); + } + + @Test + public void absentQualifiedOptionalLazy() { + assertThat(absent.qualifiedOptionalLazy()).isAbsent(); + } + + @Test + public void absentQualifiedOptionalLazyProvider() { + assertThat(absent.qualifiedOptionalLazyProvider()).isAbsent(); + } + + @Test + public void presentOptional() { + assertThat(present.optionalInstance()).hasValue(VALUE); + } + + @Test + public void presentOptionalProvider() { + assertThat(present.optionalProvider().get().get()).isEqualTo(VALUE); + } + + @Test + public void presentOptionalLazy() { + assertThat(present.optionalLazy().get().get()).isEqualTo(VALUE); + } + + @Test + public void presentOptionalLazyProvider() { + assertThat(present.optionalLazyProvider().get().get().get()).isEqualTo(VALUE); + } + + @Test + public void presentQualifiedOptional() { + assertThat(present.qualifiedOptionalInstance()).hasValue(QUALIFIED_VALUE); + } + + @Test + public void presentQualifiedOptionalProvider() { + assertThat(present.qualifiedOptionalProvider().get().get()).isEqualTo(QUALIFIED_VALUE); + } + + @Test + public void presentQualifiedOptionalLazy() { + assertThat(present.qualifiedOptionalLazy().get().get()).isEqualTo(QUALIFIED_VALUE); + } + + @Test + public void presentQualifiedOptionalLazyProvider() { + assertThat(present.qualifiedOptionalLazyProvider().get().get().get()) + .isEqualTo(QUALIFIED_VALUE); + } + + @Test + public void presentChildOptional() { + assertThat(presentChild.optionalInstance()).hasValue(VALUE); + } + + @Test + public void presentChildOptionalProvider() { + assertThat(presentChild.optionalProvider().get().get()).isEqualTo(VALUE); + } + + @Test + public void presentChildOptionalLazy() { + assertThat(presentChild.optionalLazy().get().get()).isEqualTo(VALUE); + } + + @Test + public void presentChildOptionalLazyProvider() { + assertThat(presentChild.optionalLazyProvider().get().get().get()).isEqualTo(VALUE); + } + + @Test + public void presentChildQualifiedOptional() { + assertThat(presentChild.qualifiedOptionalInstance()).hasValue(QUALIFIED_VALUE); + } + + @Test + public void presentChildQualifiedOptionalProvider() { + assertThat(presentChild.qualifiedOptionalProvider().get().get()).isEqualTo(QUALIFIED_VALUE); + } + + @Test + public void presentChildQualifiedOptionalLazy() { + assertThat(presentChild.qualifiedOptionalLazy().get().get()).isEqualTo(QUALIFIED_VALUE); + } + + @Test + public void presentChildQualifiedOptionalLazyProvider() { + assertThat(presentChild.qualifiedOptionalLazyProvider().get().get().get()) + .isEqualTo(QUALIFIED_VALUE); + } +} diff --git a/compiler/src/it/guava-functional-tests/src/test/java/test/optional/a/OptionalBindingComponentsWithInaccessibleTypesTest.java b/compiler/src/it/guava-functional-tests/src/test/java/test/optional/a/OptionalBindingComponentsWithInaccessibleTypesTest.java new file mode 100644 index 00000000000..ed655022f82 --- /dev/null +++ b/compiler/src/it/guava-functional-tests/src/test/java/test/optional/a/OptionalBindingComponentsWithInaccessibleTypesTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2016 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 test.optional.a; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for optional bindings that include types inaccessible to the component. */ +@RunWith(JUnit4.class) +public class OptionalBindingComponentsWithInaccessibleTypesTest { + @Test + public void components() { + DaggerOptionalBindingComponentsWithInaccessibleTypes_AbsentOptionalBindingComponent.create(); + DaggerOptionalBindingComponentsWithInaccessibleTypes_PresentOptionalBindingComponent.create(); + } +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/subcomponent/ModuleSubcomponentsInterop.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/subcomponent/ModuleSubcomponentsInterop.java new file mode 100644 index 00000000000..7c0c02f7333 --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/subcomponent/ModuleSubcomponentsInterop.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 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 producerstest.subcomponent; + +import dagger.Component; +import dagger.Module; +import dagger.Subcomponent; +import dagger.producers.ProducerModule; +import dagger.producers.ProductionComponent; +import dagger.producers.ProductionSubcomponent; + +final class ModuleSubcomponentsInterop { + @Component(modules = ProvisionTestModule.class) + interface ProvisionParent { + ProductionChild.Builder productionChild(); + } + + @Module(subcomponents = ProductionChild.class) + static class ProvisionTestModule {} + + @ProductionSubcomponent + interface ProductionChild { + @ProductionSubcomponent.Builder + interface Builder { + ProductionChild build(); + } + } + + @ProductionComponent(modules = ProductionTestModule.class) + interface ProductionParent { + ProvisionChild.Builder provisionBuilder(); + } + + @ProducerModule(subcomponents = ProvisionChild.class) + static class ProductionTestModule {} + + @Subcomponent + interface ProvisionChild { + @Subcomponent.Builder + interface Builder { + ProvisionChild build(); + } + } +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/subcomponent/ProductionSubcomponentFromModuleAndFactoryMethod.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/subcomponent/ProductionSubcomponentFromModuleAndFactoryMethod.java new file mode 100644 index 00000000000..c9c067aba55 --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/subcomponent/ProductionSubcomponentFromModuleAndFactoryMethod.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016 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 producerstest.subcomponent; + +import dagger.Module; +import dagger.Subcomponent; +import dagger.producers.ProducerModule; +import dagger.producers.ProductionComponent; +import dagger.producers.ProductionSubcomponent; +import producerstest.ExecutorModule; + +/** + * Tests for {@link Subcomponent}s which are defined with {@link Module#subcomponents()} and are + * also requested as component factory methods. + */ +public class ProductionSubcomponentFromModuleAndFactoryMethod { + @ProductionSubcomponent + interface Sub { + @ProductionSubcomponent.Builder + interface Builder { + Sub sub(); + } + } + + @ProducerModule(subcomponents = Sub.class) + static class ModuleWithSubcomponent {} + + @ProductionComponent(modules = {ModuleWithSubcomponent.class, ExecutorModule.class}) + interface ExposesBuilder { + Sub.Builder subcomponentBuilder(); + } +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/subcomponent/UsesProducerModuleSubcomponents.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/subcomponent/UsesProducerModuleSubcomponents.java new file mode 100644 index 00000000000..07502ffeb16 --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/subcomponent/UsesProducerModuleSubcomponents.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 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 producerstest.subcomponent; + +import com.google.common.util.concurrent.ListenableFuture; +import dagger.multibindings.IntoSet; +import dagger.producers.ProducerModule; +import dagger.producers.Produces; +import dagger.producers.ProductionComponent; +import dagger.producers.ProductionSubcomponent; +import java.util.Set; +import javax.inject.Qualifier; +import producerstest.ExecutorModule; + +/** Supporting types for {@link ProducerModuleWithSubcomponentsTest}. */ +@ProductionComponent( + modules = UsesProducerModuleSubcomponents.ProducerModuleWithSubcomponents.class +) +public interface UsesProducerModuleSubcomponents { + + ListenableFuture> strings(); + + @FromChild + ListenableFuture> stringsFromChild(); + + @ProducerModule( + subcomponents = Child.class, + includes = {AlsoIncludesSubcomponents.class, ExecutorModule.class} + ) + class ProducerModuleWithSubcomponents { + @Produces + @IntoSet + static String produceStringInParent() { + return "from parent"; + } + + @Produces + @FromChild + static Set stringsFromChild(Child.Builder childBuilder) throws Exception { + return childBuilder.build().strings().get(); + } + } + + @ProducerModule(subcomponents = Child.class) + class AlsoIncludesSubcomponents {} + + @ProductionSubcomponent(modules = ChildModule.class) + interface Child { + ListenableFuture> strings(); + + @ProductionSubcomponent.Builder + interface Builder { + Child build(); + } + } + + @ProducerModule + class ChildModule { + @Produces + @IntoSet + static String produceStringInChild() { + return "from child"; + } + } + + @Qualifier + @interface FromChild {} + + @ProducerModule(includes = ProducerModuleWithSubcomponents.class) + class OnlyIncludesProducerModuleWithSubcomponents {} + + @ProductionComponent(modules = OnlyIncludesProducerModuleWithSubcomponents.class) + interface ParentIncludesProductionSubcomponentTransitively + extends UsesProducerModuleSubcomponents {} +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/subcomponent/pruning/ParentDoesntUseProductionSubcomponent.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/subcomponent/pruning/ParentDoesntUseProductionSubcomponent.java new file mode 100644 index 00000000000..d16b1d063d7 --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/subcomponent/pruning/ParentDoesntUseProductionSubcomponent.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 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 producerstest.subcomponent.pruning; + +import com.google.common.util.concurrent.ListenableFuture; +import dagger.multibindings.IntoSet; +import dagger.producers.ProducerModule; +import dagger.producers.Produces; +import dagger.producers.ProductionComponent; +import dagger.producers.ProductionSubcomponent; +import java.util.Set; +import javax.inject.Qualifier; + +/** + * Supporting types for {@link ProductionSubcomponentOnlyRequestedBySiblingTest}. {@link ChildA} is + * a direct child of the top level component, but is only requested within its sibling, not directly + * from its parent. + */ +@ProductionComponent( + modules = { + ParentDoesntUseProductionSubcomponent.ParentModule.class, + producerstest.ExecutorModule.class + } +) +interface ParentDoesntUseProductionSubcomponent { + + ChildB.Builder childBBuilder(); + + @ProductionSubcomponent(modules = ChildAModule.class) + interface ChildA { + @ProductionSubcomponent.Builder + interface Builder { + ChildA build(); + } + + ListenableFuture>> componentHierarchy(); + } + + @ProductionSubcomponent(modules = ChildBModule.class) + interface ChildB { + @ProductionSubcomponent.Builder + interface Builder { + ChildB build(); + } + + ListenableFuture>> componentHierarchy(); + + @FromChildA + ListenableFuture>> componentHierarchyFromChildA(); + } + + @ProducerModule(subcomponents = {ChildA.class, ChildB.class}) + class ParentModule { + @Produces + @IntoSet + static Class produceComponentType() { + return ParentDoesntUseProductionSubcomponent.class; + } + } + + @ProducerModule + class ChildAModule { + @Produces + @IntoSet + static Class produceComponentType() { + return ChildA.class; + } + } + + @ProducerModule + class ChildBModule { + @Produces + @IntoSet + static Class produceComponentType() { + return ChildB.class; + } + + @Produces + @FromChildA + Set> fromChildA(ChildA.Builder childABuilder) throws Exception { + return childABuilder.build().componentHierarchy().get(); + } + } + + @Qualifier + @interface FromChildA {} +} diff --git a/compiler/src/it/producers-functional-tests/src/test/java/producerstest/subcomponent/ProducerModuleWithSubcomponentsTest.java b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/subcomponent/ProducerModuleWithSubcomponentsTest.java new file mode 100644 index 00000000000..24d43e5b679 --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/subcomponent/ProducerModuleWithSubcomponentsTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016 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 producerstest.subcomponent; + +import static com.google.common.truth.Truth.assertThat; + +import dagger.producers.ProducerModule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import producerstest.subcomponent.UsesProducerModuleSubcomponents.ParentIncludesProductionSubcomponentTransitively; + +/** Tests for {@link ProducerModule#subcomponents()}. */ +@RunWith(JUnit4.class) +public class ProducerModuleWithSubcomponentsTest { + + @Test + public void subcomponentFromModules() throws Exception { + UsesProducerModuleSubcomponents parent = DaggerUsesProducerModuleSubcomponents.create(); + assertThat(parent.strings().get()).containsExactly("from parent"); + assertThat(parent.stringsFromChild().get()).containsExactly("from parent", "from child"); + } + + @Test + public void subcomponentFromModules_transitively() throws Exception { + ParentIncludesProductionSubcomponentTransitively parent = + DaggerUsesProducerModuleSubcomponents_ParentIncludesProductionSubcomponentTransitively + .create(); + assertThat(parent.strings().get()).containsExactly("from parent"); + assertThat(parent.stringsFromChild().get()).containsExactly("from parent", "from child"); + } +} diff --git a/compiler/src/it/producers-functional-tests/src/test/java/producerstest/subcomponent/pruning/ProductionSubcomponentOnlyRequestedBySiblingTest.java b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/subcomponent/pruning/ProductionSubcomponentOnlyRequestedBySiblingTest.java new file mode 100644 index 00000000000..c579fb1716d --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/subcomponent/pruning/ProductionSubcomponentOnlyRequestedBySiblingTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016 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 producerstest.subcomponent.pruning; + +import static com.google.common.truth.Truth.assertThat; + +import dagger.producers.ProducerModule; +import dagger.producers.ProductionSubcomponent; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import producerstest.subcomponent.pruning.ParentDoesntUseProductionSubcomponent.ChildA; +import producerstest.subcomponent.pruning.ParentDoesntUseProductionSubcomponent.ChildB; + +/** + * Tests for {@link ProductionSubcomponent}s which are included with {@link + * ProducerModule#subcomponents()} but not used directly within the component which adds them. + * + *

This tests to make sure that while resolving one subcomponent (A), another subcomponent (B) + * can be requested if they have a shared ancestor component. If that shared ancestor did not + * resolve B directly via any of its entry points, B will still be generated since it is requested + * by a descendant. + */ +@RunWith(JUnit4.class) +public class ProductionSubcomponentOnlyRequestedBySiblingTest { + @Test + public void subcomponentAddedInParent_onlyUsedInSibling() throws Exception { + ParentDoesntUseProductionSubcomponent parent = + DaggerParentDoesntUseProductionSubcomponent.create(); + ChildB childB = parent.childBBuilder().build(); + assertThat(childB.componentHierarchy().get()) + .containsExactly(ParentDoesntUseProductionSubcomponent.class, ChildB.class); + assertThat(childB.componentHierarchyFromChildA().get()) + .containsExactly(ParentDoesntUseProductionSubcomponent.class, ChildA.class); + } +} diff --git a/compiler/src/main/java/dagger/internal/codegen/AbstractComponentWriter.java b/compiler/src/main/java/dagger/internal/codegen/AbstractComponentWriter.java index 64b78268e4b..4af116af33a 100644 --- a/compiler/src/main/java/dagger/internal/codegen/AbstractComponentWriter.java +++ b/compiler/src/main/java/dagger/internal/codegen/AbstractComponentWriter.java @@ -162,6 +162,13 @@ abstract class AbstractComponentWriter { */ protected final Map componentContributionFields = Maps.newHashMap(); + /** + * The factory classes that implement {@code Provider>} within the component. If the + * key is {@link Optional#absent()}, the class provides absent values. + */ + private final Map, TypeSpec> optionalFactoryClasses = + new HashMap<>(); + AbstractComponentWriter( Types types, Elements elements, @@ -570,12 +577,12 @@ private Optional staticMemberSelect(ResolvedBindings resolvedBindi generatedClassNameForBinding(contributionBinding), typeArguments)); } } + // fall through default: return Optional.of( staticMethod( - generatedClassNameForBinding(contributionBinding), - CodeBlock.of("create()"))); + generatedClassNameForBinding(contributionBinding), CodeBlock.of("create()"))); } } break; @@ -710,18 +717,16 @@ private void implementInterfaceMethods() { switch (interfaceRequest.kind()) { case MEMBERS_INJECTOR: List parameters = methodElement.getParameters(); - if (parameters.isEmpty()) { - // we're returning the framework type - interfaceMethod.addStatement("return $L", codeBlock); - } else { + if (!parameters.isEmpty()) { Name parameterName = Iterables.getOnlyElement(methodElement.getParameters()).getSimpleName(); interfaceMethod.addStatement("$L.injectMembers($L)", codeBlock, parameterName); if (!requestType.getReturnType().getKind().equals(VOID)) { interfaceMethod.addStatement("return $L", parameterName); } + break; } - break; + // fall through default: interfaceMethod.addStatement("return $L", codeBlock); break; @@ -765,9 +770,14 @@ public MethodSpec.Builder methodSpecForComponentMethod( } private void addSubcomponents() { - for (Map.Entry subgraphEntry : graph.subgraphs().entrySet()) { + for (BindingGraph subgraph : graph.subgraphs()) { + ComponentMethodDescriptor componentMethodDescriptor = + graph.componentDescriptor() + .subcomponentsByFactoryMethod() + .inverse() + .get(subgraph.componentDescriptor()); SubcomponentWriter subcomponent = - new SubcomponentWriter(this, subgraphEntry.getKey(), subgraphEntry.getValue()); + new SubcomponentWriter(this, Optional.fromNullable(componentMethodDescriptor), subgraph); component.addType(subcomponent.write().build()); } } @@ -1023,17 +1033,22 @@ private CodeBlock initializeFactoryForContributionBinding(ContributionBinding bi } case SUBCOMPONENT_BUILDER: + String subcomponentName = + subcomponentNames.get( + graph.componentDescriptor() + .subcomponentsByBuilderType() + .get(MoreTypes.asTypeElement(binding.key().type()))); return CodeBlock.of( Joiner.on('\n') .join( "new $1T<$2T>() {", " @Override public $2T get() {", - " return $3L();", + " return new $3LBuilder();", " }", "}"), /* 1 */ FACTORY, /* 2 */ bindingKeyTypeName, - /* 3 */ binding.bindingElement().get().getSimpleName()); + /* 3 */ subcomponentName); case INJECTION: case PROVISION: @@ -1104,8 +1119,11 @@ private CodeBlock initializeFactoryForContributionBinding(ContributionBinding bi case SYNTHETIC_MULTIBOUND_MAP: return initializeFactoryForMapMultibinding(binding); + case SYNTHETIC_OPTIONAL_BINDING: + return initializeFactoryForSyntheticOptionalBinding(binding); + default: - throw new AssertionError(binding.toString()); + throw new AssertionError(binding); } } @@ -1146,22 +1164,20 @@ private CodeBlock initializeMembersInjectorForBinding(MembersInjectionBinding bi private ImmutableList getDependencyArguments(Binding binding) { ImmutableList.Builder parameters = ImmutableList.builder(); for (FrameworkDependency frameworkDependency : frameworkDependenciesForBinding(binding)) { - parameters.add(getDependencyArgument(frameworkDependency)); + parameters.add(getDependencyArgument(frameworkDependency).getExpressionFor(name)); } return parameters.build(); } - /** - * The expression to use as an argument for a dependency. - */ - private CodeBlock getDependencyArgument(FrameworkDependency frameworkDependency) { + /** Returns the member select to use as an argument for a dependency. */ + private MemberSelect getDependencyArgument(FrameworkDependency frameworkDependency) { BindingKey requestedKey = frameworkDependency.bindingKey(); ResolvedBindings resolvedBindings = graph.resolvedBindings().get(requestedKey); if (resolvedBindings.frameworkClass().equals(Provider.class) && frameworkDependency.frameworkClass().equals(Producer.class)) { - return producerFromProviderMemberSelects.get(requestedKey).getExpressionFor(name); + return producerFromProviderMemberSelects.get(requestedKey); } else { - return getMemberSelectExpression(requestedKey); + return getMemberSelect(requestedKey); } } @@ -1204,7 +1220,7 @@ private CodeBlock initializeFactoryForSetMultibinding(ContributionBinding bindin potentiallyCast( useRawTypes, frameworkDependency.frameworkClass(), - getDependencyArgument(frameworkDependency))); + getDependencyArgument(frameworkDependency).getExpressionFor(name))); } builder.add("builder($L, $L)", individualProviders, setProviders); builder.add(builderMethodCalls.build()); @@ -1235,7 +1251,7 @@ private CodeBlock initializeFactoryForMapMultibinding(ContributionBinding bindin potentiallyCast( useRawTypes, frameworkDependency.frameworkClass(), - getDependencyArgument(frameworkDependency)); + getDependencyArgument(frameworkDependency).getExpressionFor(name)); if (binding.bindingType().frameworkClass().equals(Producer.class) && frameworkDependency.frameworkClass().equals(Provider.class)) { value = CodeBlock.of("$T.producerFromProvider($L)", PRODUCERS, value); @@ -1256,6 +1272,50 @@ private CodeBlock potentiallyCast(boolean shouldCast, Class classToCast, Code return CodeBlock.of("($T) $L", classToCast, notCasted); } + /** Returns an expression that initializes a {@link Provider} for an optional binding. */ + private CodeBlock initializeFactoryForSyntheticOptionalBinding(ContributionBinding binding) { + if (binding.bindingType().equals(BindingType.PRODUCTION)) { + throw new UnsupportedOperationException("optional producers are not supported yet"); + } + + if (binding.dependencies().isEmpty()) { + return CodeBlock.of( + "$N.instance()", optionalFactoryClass(Optional.absent())); + } else { + TypeMirror valueType = OptionalType.from(binding.key()).valueType(); + DependencyRequest.Kind optionalValueKind = + DependencyRequest.extractKindAndType(valueType).kind(); + FrameworkDependency frameworkDependency = + getOnlyElement(frameworkDependenciesForBinding(binding)); + CodeBlock dependencyArgument = + getDependencyArgument(frameworkDependency).getExpressionFor(name); + return CodeBlock.of( + "$N.of($L)", optionalFactoryClass(Optional.of(optionalValueKind)), dependencyArgument); + } + } + + /** + * Returns the nested class that implements {@code Provider>} for optional bindings. + * Adds it to the root component if it hasn't already been added. + * + *

If {@code optionalValueKind} is absent, returns a {@link Provider} class that returns {@link + * Optional#absent()}. + * + *

If {@code optionalValueKind} is present, returns a {@link Provider} class where {@code T} + * represents dependency requests of that kind. + */ + protected TypeSpec optionalFactoryClass(Optional optionalValueKind) { + if (!optionalFactoryClasses.containsKey(optionalValueKind)) { + TypeSpec factory = + optionalValueKind.isPresent() + ? OptionalFactoryClasses.presentFactoryClass(optionalValueKind.get()) + : OptionalFactoryClasses.ABSENT_FACTORY_CLASS; + component.addType(factory); + optionalFactoryClasses.put(optionalValueKind, factory); + } + return optionalFactoryClasses.get(optionalValueKind); + } + private static String simpleVariableName(TypeElement typeElement) { return UPPER_CAMEL.to(LOWER_CAMEL, typeElement.getSimpleName().toString()); } diff --git a/compiler/src/main/java/dagger/internal/codegen/MapKeyGenerator.java b/compiler/src/main/java/dagger/internal/codegen/AnnotationCreatorGenerator.java similarity index 56% rename from compiler/src/main/java/dagger/internal/codegen/MapKeyGenerator.java rename to compiler/src/main/java/dagger/internal/codegen/AnnotationCreatorGenerator.java index 22c329b482b..b4d3726c3c0 100644 --- a/compiler/src/main/java/dagger/internal/codegen/MapKeyGenerator.java +++ b/compiler/src/main/java/dagger/internal/codegen/AnnotationCreatorGenerator.java @@ -20,7 +20,7 @@ import static com.squareup.javapoet.MethodSpec.methodBuilder; import static com.squareup.javapoet.TypeSpec.classBuilder; import static dagger.internal.codegen.CodeBlocks.makeParametersCodeBlock; -import static dagger.internal.codegen.MapKeys.getMapKeyCreatorClassName; +import static dagger.internal.codegen.SourceFiles.classFileName; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; @@ -29,7 +29,6 @@ import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoAnnotation; -import com.google.auto.value.AutoValue; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -38,8 +37,6 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import dagger.MapKey; -import dagger.internal.codegen.MapKeyGenerator.MapKeyCreatorSpecification; import java.util.LinkedHashSet; import java.util.Set; import javax.annotation.processing.Filer; @@ -52,81 +49,75 @@ import javax.lang.model.util.SimpleTypeVisitor6; /** - * Generates classes that create annotations required to instantiate {@link MapKey}s. + * Generates classes that create annotation instances for an annotation type. The generated class + * will have a private empty constructor, a static method that creates the annotation type itself, + * and a static method that creates each annotation type that is nested in the top-level annotation + * type. * - * @since 2.0 + *

So for an example annotation: + * + *

+ *   {@literal @interface} Foo {
+ *     String s();
+ *     int i();
+ *     Bar bar(); // an annotation defined elsewhere
+ *   }
+ * 
+ * + * the generated class will look like: + * + *
+ *   public final class FooCreator {
+ *     private FooCreator() {}
+ *
+ *     public static Foo createFoo(String s, int i, Bar bar) { … }
+ *     public static Bar createBar(…) { … }
+ *   }
+ * 
*/ -final class MapKeyGenerator extends SourceFileGenerator { +class AnnotationCreatorGenerator extends SourceFileGenerator { /** - * Specification of the {@link MapKey} annotation and the annotation type to generate. + * Returns the name of the generated class that contains the static {@code create} methods for an + * annotation type. */ - @AutoValue - abstract static class MapKeyCreatorSpecification { - /** - * The {@link MapKey}-annotated annotation. - */ - abstract TypeElement mapKeyElement(); - - /** - * The annotation type to write create methods for. For wrapped {@link MapKey}s, this is - * {@link #mapKeyElement()}. For unwrapped {@code MapKey}s whose single element is an - * annotation, this is that annotation element. - */ - abstract TypeElement annotationElement(); - - /** - * Returns a specification for a wrapped {@link MapKey}-annotated annotation. - */ - static MapKeyCreatorSpecification wrappedMapKey(TypeElement mapKeyElement) { - return new AutoValue_MapKeyGenerator_MapKeyCreatorSpecification(mapKeyElement, mapKeyElement); - } - - /** - * Returns a specification for an unwrapped {@link MapKey}-annotated annotation whose single - * element is a nested annotation. - */ - static MapKeyCreatorSpecification unwrappedMapKeyWithAnnotationValue( - TypeElement mapKeyElement, TypeElement annotationElement) { - return new AutoValue_MapKeyGenerator_MapKeyCreatorSpecification( - mapKeyElement, annotationElement); - } + static ClassName getAnnotationCreatorClassName(TypeElement annotationType) { + ClassName annotationTypeName = ClassName.get(annotationType); + return annotationTypeName + .topLevelClassName() + .peerClass(classFileName(annotationTypeName) + "Creator"); } - MapKeyGenerator(Filer filer, Elements elements) { + AnnotationCreatorGenerator(Filer filer, Elements elements) { super(filer, elements); } @Override - ClassName nameGeneratedType(MapKeyCreatorSpecification mapKeyCreatorType) { - return getMapKeyCreatorClassName(mapKeyCreatorType.mapKeyElement()); + ClassName nameGeneratedType(TypeElement annotationType) { + return getAnnotationCreatorClassName(annotationType); } @Override - Optional getElementForErrorReporting( - MapKeyCreatorSpecification mapKeyCreatorType) { - return Optional.of(mapKeyCreatorType.mapKeyElement()); + Optional getElementForErrorReporting(TypeElement annotationType) { + return Optional.of(annotationType); } @Override - Optional write( - ClassName generatedTypeName, MapKeyCreatorSpecification mapKeyCreatorType) { - TypeSpec.Builder mapKeyCreatorBuilder = - classBuilder(generatedTypeName).addModifiers(PUBLIC, FINAL); - - mapKeyCreatorBuilder.addMethod(constructorBuilder().addModifiers(PRIVATE).build()); - - for (TypeElement annotationElement : - nestedAnnotationElements(mapKeyCreatorType.annotationElement())) { - mapKeyCreatorBuilder.addMethod(buildCreateMethod(generatedTypeName, annotationElement)); + Optional write(ClassName generatedTypeName, TypeElement annotationType) { + TypeSpec.Builder annotationCreatorBuilder = + classBuilder(generatedTypeName) + .addModifiers(PUBLIC, FINAL) + .addMethod(constructorBuilder().addModifiers(PRIVATE).build()); + + for (TypeElement annotationElement : annotationsToCreate(annotationType)) { + annotationCreatorBuilder.addMethod(buildCreateMethod(generatedTypeName, annotationElement)); } - return Optional.of(mapKeyCreatorBuilder); + return Optional.of(annotationCreatorBuilder); } - private MethodSpec buildCreateMethod( - ClassName mapKeyGeneratedTypeName, TypeElement annotationElement) { - String createMethodName = "create" + annotationElement.getSimpleName(); + private MethodSpec buildCreateMethod(ClassName generatedTypeName, TypeElement annotationElement) { + String createMethodName = createMethodName(annotationElement); MethodSpec.Builder createMethod = methodBuilder(createMethodName) .addAnnotation(AutoAnnotation.class) @@ -141,14 +132,23 @@ private MethodSpec buildCreateMethod( parameters.add(CodeBlock.of("$L", parameterName)); } - ClassName autoAnnotationClass = mapKeyGeneratedTypeName.peerClass( - "AutoAnnotation_" + mapKeyGeneratedTypeName.simpleName() + "_" + createMethodName); + ClassName autoAnnotationClass = + generatedTypeName.peerClass( + "AutoAnnotation_" + generatedTypeName.simpleName() + "_" + createMethodName); createMethod.addStatement( "return new $T($L)", autoAnnotationClass, makeParametersCodeBlock(parameters.build())); return createMethod.build(); } - private static Set nestedAnnotationElements(TypeElement annotationElement) { + static String createMethodName(TypeElement annotationType) { + return "create" + annotationType.getSimpleName(); + } + + /** + * Returns the annotation types for which {@code @AutoAnnotation static Foo createFoo(…)} methods + * should be written. + */ + protected Set annotationsToCreate(TypeElement annotationElement) { return nestedAnnotationElements(annotationElement, new LinkedHashSet()); } diff --git a/compiler/src/main/java/dagger/internal/codegen/AnnotationExpression.java b/compiler/src/main/java/dagger/internal/codegen/AnnotationExpression.java new file mode 100644 index 00000000000..a3bb91caf03 --- /dev/null +++ b/compiler/src/main/java/dagger/internal/codegen/AnnotationExpression.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2016 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.auto.common.AnnotationMirrors.getAnnotationValuesWithDefaults; +import static com.google.common.collect.Iterables.transform; +import static dagger.internal.codegen.CodeBlocks.makeParametersCodeBlock; + +import com.google.auto.common.MoreElements; +import com.google.auto.common.MoreTypes; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.TypeName; +import java.util.List; +import java.util.Map; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleAnnotationValueVisitor6; +import javax.lang.model.util.SimpleTypeVisitor6; + +/** + * Returns an expression creating an instance of the visited annotation type. Its parameter must be + * a class as generated by {@link AnnotationCreatorGenerator}. + * + *

Note that {@link AnnotationValue#toString()} is the source-code representation of the value + * when used in an annotation, which is not always the same as the representation needed + * when creating the value in a method body. + * + *

For example, inside an annotation, a nested array of {@code int}s is simply {@code {1, 2, 3}}, + * but in code it would have to be {@code new int[] {1, 2, 3}}. + */ +class AnnotationExpression extends SimpleAnnotationValueVisitor6 { + + private final AnnotationMirror annotation; + private final ClassName creatorClass; + + AnnotationExpression(AnnotationMirror annotation) { + this.annotation = annotation; + this.creatorClass = + AnnotationCreatorGenerator.getAnnotationCreatorClassName( + MoreTypes.asTypeElement(annotation.getAnnotationType())); + } + + /** + * Returns an expression that calls static methods on the annotation's creator class to create an + * annotation instance equivalent the annotation passed to the constructor. + */ + CodeBlock getAnnotationInstanceExpression() { + return getAnnotationInstanceExpression(annotation); + } + + private CodeBlock getAnnotationInstanceExpression(AnnotationMirror annotation) { + return CodeBlock.of( + "$T.$L($L)", + creatorClass, + AnnotationCreatorGenerator.createMethodName( + MoreElements.asType(annotation.getAnnotationType().asElement())), + makeParametersCodeBlock( + transform( + getAnnotationValuesWithDefaults(annotation).entrySet(), + new Function, CodeBlock>() { + @Override + public CodeBlock apply(Map.Entry entry) { + return getValueExpression(entry.getKey().getReturnType(), entry.getValue()); + } + }))); + } + + /** + * Returns an expression that evaluates to a {@code value} of a given type on an {@code + * annotation}. + */ + CodeBlock getValueExpression(TypeMirror valueType, AnnotationValue value) { + return ARRAY_LITERAL_PREFIX.visit(valueType, this.visit(value, value)); + } + + @Override + public CodeBlock visitEnumConstant(VariableElement c, AnnotationValue p) { + return CodeBlock.of("$T.$L", TypeName.get(c.getEnclosingElement().asType()), c.getSimpleName()); + } + + @Override + public CodeBlock visitAnnotation(AnnotationMirror a, AnnotationValue p) { + return getAnnotationInstanceExpression(a); + } + + @Override + public CodeBlock visitType(TypeMirror t, AnnotationValue p) { + return CodeBlock.of("$T.class", TypeName.get(t)); + } + + @Override + public CodeBlock visitString(String s, AnnotationValue p) { + return CodeBlock.of("$S", s); + } + + @Override + public CodeBlock visitByte(byte b, AnnotationValue p) { + return CodeBlock.of("(byte) $L", b); + } + + @Override + public CodeBlock visitChar(char c, AnnotationValue p) { + return CodeBlock.of("$L", p); + } + + @Override + public CodeBlock visitDouble(double d, AnnotationValue p) { + return CodeBlock.of("$LD", d); + } + + @Override + public CodeBlock visitFloat(float f, AnnotationValue p) { + return CodeBlock.of("$LF", f); + } + + @Override + public CodeBlock visitLong(long i, AnnotationValue p) { + return CodeBlock.of("$LL", i); + } + + @Override + public CodeBlock visitShort(short s, AnnotationValue p) { + return CodeBlock.of("(short) $L", s); + } + + @Override + protected CodeBlock defaultAction(Object o, AnnotationValue p) { + return CodeBlock.of("$L", o); + } + + @Override + public CodeBlock visitArray(List values, AnnotationValue p) { + ImmutableList.Builder codeBlocks = ImmutableList.builder(); + for (AnnotationValue value : values) { + codeBlocks.add(this.visit(value, p)); + } + return CodeBlock.of("{$L}", makeParametersCodeBlock(codeBlocks.build())); + } + + /** + * If the visited type is an array, prefixes the parameter code block with {@code new T[]}, where + * {@code T} is the raw array component type. + */ + private static final SimpleTypeVisitor6 ARRAY_LITERAL_PREFIX = + new SimpleTypeVisitor6() { + + @Override + public CodeBlock visitArray(ArrayType t, CodeBlock p) { + return CodeBlock.of("new $T[] $L", RAW_TYPE_NAME.visit(t.getComponentType()), p); + } + + @Override + protected CodeBlock defaultAction(TypeMirror e, CodeBlock p) { + return p; + } + }; + + /** + * If the visited type is an array, returns the name of its raw component type; otherwise returns + * the name of the type itself. + */ + private static final SimpleTypeVisitor6 RAW_TYPE_NAME = + new SimpleTypeVisitor6() { + @Override + public TypeName visitDeclared(DeclaredType t, Void p) { + return ClassName.get(MoreTypes.asTypeElement(t)); + } + + @Override + protected TypeName defaultAction(TypeMirror e, Void p) { + return TypeName.get(e); + } + }; +} diff --git a/compiler/src/main/java/dagger/internal/codegen/BindingDeclarationFormatter.java b/compiler/src/main/java/dagger/internal/codegen/BindingDeclarationFormatter.java index b88c7cbec85..d2aece6d3a7 100644 --- a/compiler/src/main/java/dagger/internal/codegen/BindingDeclarationFormatter.java +++ b/compiler/src/main/java/dagger/internal/codegen/BindingDeclarationFormatter.java @@ -17,11 +17,17 @@ package dagger.internal.codegen; import static com.google.common.base.Preconditions.checkArgument; +import static dagger.internal.codegen.ConfigurationAnnotations.getModuleSubcomponents; import static dagger.internal.codegen.ErrorMessages.stripCommonTypePrefixes; +import static dagger.internal.codegen.MoreAnnotationMirrors.simpleName; import static dagger.internal.codegen.Util.AS_DECLARED_TYPE; import com.google.auto.common.MoreElements; +import com.google.auto.common.MoreTypes; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; /** * Formats a {@link BindingDeclaration} into a {@link String} suitable for use in error messages. @@ -35,10 +41,14 @@ final class BindingDeclarationFormatter extends Formatter { @Override public String format(BindingDeclaration bindingDeclaration) { + if (bindingDeclaration instanceof SubcomponentDeclaration) { + return formatSubcomponentDeclaration((SubcomponentDeclaration) bindingDeclaration); + } checkArgument( bindingDeclaration.bindingElement().isPresent(), "Cannot format bindings without source elements: %s", bindingDeclaration); + Element bindingElement = bindingDeclaration.bindingElement().get(); switch (bindingElement.asType().getKind()) { case EXECUTABLE: @@ -51,4 +61,32 @@ public String format(BindingDeclaration bindingDeclaration) { throw new IllegalArgumentException("Formatting unsupported for element: " + bindingElement); } } + + private String formatSubcomponentDeclaration(SubcomponentDeclaration subcomponentDeclaration) { + ImmutableList moduleSubcomponents = + getModuleSubcomponents(subcomponentDeclaration.moduleAnnotation()); + int index = + Iterables.indexOf( + moduleSubcomponents, + MoreTypes.equivalence() + .equivalentTo(subcomponentDeclaration.subcomponentType().asType())); + StringBuilder annotationValue = new StringBuilder(); + if (moduleSubcomponents.size() != 1) { + annotationValue.append("{"); + } + annotationValue.append( + formatArgumentInList( + index, + moduleSubcomponents.size(), + subcomponentDeclaration.subcomponentType().getQualifiedName() + ".class")); + if (moduleSubcomponents.size() != 1) { + annotationValue.append("}"); + } + + return String.format( + "@%s(subcomponents = %s) for %s", + simpleName(subcomponentDeclaration.moduleAnnotation()), + annotationValue, + subcomponentDeclaration.contributingModule().get()); + } } diff --git a/compiler/src/main/java/dagger/internal/codegen/BindingGraph.java b/compiler/src/main/java/dagger/internal/codegen/BindingGraph.java index ab91f9db40d..23c9e94a430 100644 --- a/compiler/src/main/java/dagger/internal/codegen/BindingGraph.java +++ b/compiler/src/main/java/dagger/internal/codegen/BindingGraph.java @@ -23,16 +23,15 @@ import static com.google.common.base.Predicates.in; import static com.google.common.base.Predicates.not; import static com.google.common.base.Verify.verify; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Iterables.isEmpty; import static dagger.internal.codegen.BindingType.isOfType; -import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor.isOfKind; -import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodKind.PRODUCTION_SUBCOMPONENT_BUILDER; -import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodKind.SUBCOMPONENT_BUILDER; import static dagger.internal.codegen.ComponentDescriptor.Kind.PRODUCTION_COMPONENT; import static dagger.internal.codegen.ComponentDescriptor.isComponentContributionMethod; import static dagger.internal.codegen.ComponentDescriptor.isComponentProductionMethod; import static dagger.internal.codegen.ConfigurationAnnotations.getComponentDependencies; import static dagger.internal.codegen.ContributionBinding.Kind.IS_SYNTHETIC_MULTIBINDING_KIND; +import static dagger.internal.codegen.ContributionBinding.Kind.SYNTHETIC_OPTIONAL_BINDING; import static dagger.internal.codegen.Key.indexByKey; import static dagger.internal.codegen.Scope.reusableScope; import static javax.lang.model.element.Modifier.ABSTRACT; @@ -41,6 +40,7 @@ import com.google.auto.value.AutoValue; import com.google.common.base.Function; import com.google.common.base.Optional; +import com.google.common.base.Predicate; import com.google.common.base.VerifyException; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @@ -58,6 +58,7 @@ import dagger.Reusable; import dagger.Subcomponent; import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor; +import dagger.internal.codegen.ContributionBinding.Kind; import dagger.internal.codegen.Key.HasKey; import dagger.producers.Produced; import dagger.producers.Producer; @@ -69,7 +70,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; +import java.util.Queue; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -90,7 +91,7 @@ abstract class BindingGraph { abstract ComponentDescriptor componentDescriptor(); abstract ImmutableMap resolvedBindings(); - abstract ImmutableMap subgraphs(); + abstract ImmutableSet subgraphs(); /** * Returns the set of modules that are owned by this graph regardless of whether or not any of @@ -112,7 +113,7 @@ ImmutableSet ownedModuleTypes() { new TreeTraverser() { @Override public Iterable children(BindingGraph node) { - return node.subgraphs().values(); + return node.subgraphs(); } }; @@ -148,18 +149,17 @@ public Iterable apply(BindingGraph graph) { * Returns the {@link ComponentDescriptor}s for this component and its subcomponents. */ ImmutableSet componentDescriptors() { - return SUBGRAPH_TRAVERSER - .preOrderTraversal(this) - .transform( - new Function() { - @Override - public ComponentDescriptor apply(BindingGraph graph) { - return graph.componentDescriptor(); - } - }) - .toSet(); + return SUBGRAPH_TRAVERSER.preOrderTraversal(this).transform(COMPONENT_DESCRIPTOR).toSet(); } + static final Function COMPONENT_DESCRIPTOR = + new Function() { + @Override + public ComponentDescriptor apply(BindingGraph graph) { + return graph.componentDescriptor(); + } + }; + ImmutableSet availableDependencies() { return FluentIterable.from(componentDescriptor().transitiveModuleTypes()) .filter(not(hasModifiers(ABSTRACT))) @@ -174,7 +174,8 @@ static final class Factory { private final ProvisionBinding.Factory provisionBindingFactory; private final ProductionBinding.Factory productionBindingFactory; - Factory(Elements elements, + Factory( + Elements elements, InjectBindingRegistry injectBindingRegistry, Key.Factory keyFactory, ProvisionBinding.Factory provisionBindingFactory, @@ -194,6 +195,7 @@ private BindingGraph create( Optional parentResolver, ComponentDescriptor componentDescriptor) { ImmutableSet.Builder explicitBindingsBuilder = ImmutableSet.builder(); ImmutableSet.Builder delegatesBuilder = ImmutableSet.builder(); + ImmutableSet.Builder optionalsBuilder = ImmutableSet.builder(); // binding for the component itself TypeElement componentDefinitionType = componentDescriptor.componentDefinitionType(); @@ -222,34 +224,42 @@ && isComponentProductionMethod(elements, method) } } - // Bindings for subcomponent builders. - for (ComponentMethodDescriptor subcomponentMethodDescriptor : - Iterables.filter( - componentDescriptor.subcomponents().keySet(), - isOfKind(SUBCOMPONENT_BUILDER, PRODUCTION_SUBCOMPONENT_BUILDER))) { - explicitBindingsBuilder.add( - provisionBindingFactory.forSubcomponentBuilderMethod( - subcomponentMethodDescriptor.methodElement(), - componentDescriptor.componentDefinitionType())); + for (Map.Entry + componentMethodAndSubcomponent : + componentDescriptor.subcomponentsByBuilderMethod().entrySet()) { + ComponentMethodDescriptor componentMethod = componentMethodAndSubcomponent.getKey(); + ComponentDescriptor subcomponentDescriptor = componentMethodAndSubcomponent.getValue(); + if (!componentDescriptor.subcomponentsFromModules().contains(subcomponentDescriptor)) { + explicitBindingsBuilder.add( + provisionBindingFactory.forSubcomponentBuilderMethod( + componentMethod.methodElement(), + componentDescriptor.componentDefinitionType())); + } } ImmutableSet.Builder multibindingDeclarations = ImmutableSet.builder(); + ImmutableSet.Builder subcomponentDeclarations = + ImmutableSet.builder(); // Collect transitive module bindings and multibinding declarations. for (ModuleDescriptor moduleDescriptor : componentDescriptor.transitiveModules()) { explicitBindingsBuilder.addAll(moduleDescriptor.bindings()); multibindingDeclarations.addAll(moduleDescriptor.multibindingDeclarations()); + subcomponentDeclarations.addAll(moduleDescriptor.subcomponentDeclarations()); delegatesBuilder.addAll(moduleDescriptor.delegateDeclarations()); + optionalsBuilder.addAll(moduleDescriptor.optionalDeclarations()); } - Resolver requestResolver = + final Resolver requestResolver = new Resolver( parentResolver, componentDescriptor, indexByKey(explicitBindingsBuilder.build()), indexByKey(multibindingDeclarations.build()), - indexByKey(delegatesBuilder.build())); + indexByKey(subcomponentDeclarations.build()), + indexByKey(delegatesBuilder.build()), + indexByKey(optionalsBuilder.build())); for (ComponentMethodDescriptor componentMethod : componentDescriptor.componentMethods()) { Optional componentMethodRequest = componentMethod.dependencyRequest(); if (componentMethodRequest.isPresent()) { @@ -257,13 +267,18 @@ && isComponentProductionMethod(elements, method) } } - ImmutableMap.Builder subgraphsBuilder = - ImmutableMap.builder(); - for (Entry subcomponentEntry : - componentDescriptor.subcomponents().entrySet()) { - subgraphsBuilder.put( - subcomponentEntry.getKey().methodElement(), - create(Optional.of(requestResolver), subcomponentEntry.getValue())); + // Resolve all bindings for subcomponents, creating subgraphs for all subcomponents that have + // been detected during binding resolution. If a binding for a subcomponent is never resolved, + // no BindingGraph will be created for it and no implementation will be generated. This is + // done in a queue since resolving one subcomponent might resolve a key for a subcomponent + // from a parent graph. This is done until no more new subcomponents are resolved. + Set resolvedSubcomponents = new HashSet<>(); + ImmutableSet.Builder subgraphs = ImmutableSet.builder(); + for (ComponentDescriptor subcomponent : + Iterables.consumingIterable(requestResolver.subcomponentsToResolve)) { + if (resolvedSubcomponents.add(subcomponent)) { + subgraphs.add(create(Optional.of(requestResolver), subcomponent)); + } } for (ResolvedBindings resolvedBindings : requestResolver.getResolvedBindings().values()) { @@ -277,7 +292,7 @@ && isComponentProductionMethod(elements, method) return new AutoValue_BindingGraph( componentDescriptor, requestResolver.getResolvedBindings(), - subgraphsBuilder.build(), + subgraphs.build(), requestResolver.getOwnedModules()); } @@ -288,7 +303,9 @@ private final class Resolver { final ImmutableSet explicitBindingsSet; final ImmutableSetMultimap explicitMultibindings; final ImmutableSetMultimap multibindingDeclarations; + final ImmutableSetMultimap subcomponentDeclarations; final ImmutableSetMultimap delegateDeclarations; + final ImmutableSetMultimap optionalBindingDeclarations; final ImmutableSetMultimap delegateMultibindingDeclarations; final Map resolvedBindings; final Deque cycleStack = new ArrayDeque<>(); @@ -296,24 +313,30 @@ private final class Resolver { CacheBuilder.newBuilder().build(); final Cache bindingDependsOnLocalMultibindingsCache = CacheBuilder.newBuilder().build(); + final Queue subcomponentsToResolve = new ArrayDeque<>(); Resolver( Optional parentResolver, ComponentDescriptor componentDescriptor, ImmutableSetMultimap explicitBindings, ImmutableSetMultimap multibindingDeclarations, - ImmutableSetMultimap delegateDeclarations) { + ImmutableSetMultimap subcomponentDeclarations, + ImmutableSetMultimap delegateDeclarations, + ImmutableSetMultimap optionalBindingDeclarations) { this.parentResolver = checkNotNull(parentResolver); this.componentDescriptor = checkNotNull(componentDescriptor); this.explicitBindings = checkNotNull(explicitBindings); this.explicitBindingsSet = ImmutableSet.copyOf(explicitBindings.values()); this.multibindingDeclarations = checkNotNull(multibindingDeclarations); + this.subcomponentDeclarations = checkNotNull(subcomponentDeclarations); this.delegateDeclarations = checkNotNull(delegateDeclarations); + this.optionalBindingDeclarations = checkNotNull(optionalBindingDeclarations); this.resolvedBindings = Maps.newLinkedHashMap(); this.explicitMultibindings = multibindingContributionsByMultibindingKey(explicitBindingsSet); this.delegateMultibindingDeclarations = multibindingContributionsByMultibindingKey(delegateDeclarations.values()); + subcomponentsToResolve.addAll(componentDescriptor.subcomponentsFromEntryPoints()); } /** @@ -352,23 +375,41 @@ ResolvedBindings lookUpBindings(BindingKey bindingKey) { ImmutableSet.builder(); ImmutableSet.Builder multibindingDeclarationsBuilder = ImmutableSet.builder(); + ImmutableSet.Builder subcomponentDeclarationsBuilder = + ImmutableSet.builder(); + ImmutableSet.Builder optionalBindingDeclarationsBuilder = + ImmutableSet.builder(); for (Key key : keysMatchingRequest(requestKey)) { contributionBindings.addAll(getExplicitBindings(key)); multibindingContributionsBuilder.addAll(getExplicitMultibindings(key)); multibindingDeclarationsBuilder.addAll(getMultibindingDeclarations(key)); + subcomponentDeclarationsBuilder.addAll(getSubcomponentDeclarations(key)); + optionalBindingDeclarationsBuilder.addAll(getOptionalBindingDeclarations(key)); } ImmutableSet multibindingContributions = multibindingContributionsBuilder.build(); ImmutableSet multibindingDeclarations = multibindingDeclarationsBuilder.build(); + ImmutableSet subcomponentDeclarations = + subcomponentDeclarationsBuilder.build(); + ImmutableSet optionalBindingDeclarations = + optionalBindingDeclarationsBuilder.build(); - contributionBindings.addAll(syntheticMapOfValuesBinding(bindingKey.key()).asSet()); + contributionBindings.addAll(syntheticMapOfValuesBinding(requestKey).asSet()); contributionBindings.addAll( syntheticMultibinding( - bindingKey.key(), multibindingContributions, multibindingDeclarations) + requestKey, multibindingContributions, multibindingDeclarations) .asSet()); + Optional subcomponentBuilderBinding = + syntheticSubcomponentBuilderBinding(subcomponentDeclarations); + if (subcomponentBuilderBinding.isPresent()) { + contributionBindings.add(subcomponentBuilderBinding.get()); + addSubcomponentToOwningResolver(subcomponentBuilderBinding.get()); + } + contributionBindings.addAll( + syntheticOptionalBinding(requestKey, optionalBindingDeclarations).asSet()); /* If there are no bindings, add the implicit @Inject-constructed binding if there is * one. */ @@ -382,7 +423,9 @@ ResolvedBindings lookUpBindings(BindingKey bindingKey) { componentDescriptor, indexBindingsByOwningComponent( bindingKey, ImmutableSet.copyOf(contributionBindings)), - multibindingDeclarations); + multibindingDeclarations, + subcomponentDeclarations, + optionalBindingDeclarations); case MEMBERS_INJECTION: // no explicit deps for members injection, so just look it up @@ -398,6 +441,20 @@ ResolvedBindings lookUpBindings(BindingKey bindingKey) { } } + /** + * When a binding is resolved for a {@link SubcomponentDeclaration}, adds corresponding + * {@link ComponentDescriptor subcomponent} to a queue in the owning component's resolver. + * The queue will be used to detect which subcomponents need to be resolved. + */ + private void addSubcomponentToOwningResolver(ProvisionBinding subcomponentBuilderBinding) { + checkArgument(subcomponentBuilderBinding.bindingKind().equals(Kind.SUBCOMPONENT_BUILDER)); + Resolver owningResolver = getOwningResolver(subcomponentBuilderBinding).get(); + + TypeElement builderType = MoreTypes.asTypeElement(subcomponentBuilderBinding.key().type()); + owningResolver.subcomponentsToResolve.add( + owningResolver.componentDescriptor.subcomponentsByBuilderType().get(builderType)); + } + private Iterable keysMatchingRequest(Key requestKey) { return ImmutableSet.builder() .add(requestKey) @@ -521,6 +578,34 @@ private boolean multibindingsRequireProduction( return Iterables.any(multibindingContributions, isOfType(BindingType.PRODUCTION)); } + private Optional syntheticSubcomponentBuilderBinding( + ImmutableSet subcomponentDeclarations) { + return subcomponentDeclarations.isEmpty() + ? Optional.absent() + : Optional.of( + provisionBindingFactory.syntheticSubcomponentBuilder(subcomponentDeclarations)); + } + + /** + * Returns a synthetic binding for {@code @Qualifier Optional} if there are any {@code + * optionalBindingDeclarations}. + */ + private Optional syntheticOptionalBinding( + Key key, ImmutableSet optionalBindingDeclarations) { + if (optionalBindingDeclarations.isEmpty()) { + return Optional.absent(); + } + ContributionBinding syntheticPresentBinding = + provisionBindingFactory.syntheticPresentBinding(key); + ResolvedBindings bindings = + lookUpBindings(getOnlyElement(syntheticPresentBinding.dependencies()).bindingKey()); + if (bindings.isEmpty()) { + return Optional.of(provisionBindingFactory.syntheticAbsentBinding(key)); + } else { // TODO(dpb): Support producers. + return Optional.of(syntheticPresentBinding); + } + } + private ImmutableSet createDelegateBindings( ImmutableSet delegateDeclarations) { ImmutableSet.Builder builder = ImmutableSet.builder(); @@ -632,7 +717,8 @@ private Optional getOwningResolver(ContributionBinding binding) { } for (Resolver requestResolver : getResolverLineage().reverse()) { - if (requestResolver.explicitBindingsSet.contains(binding)) { + if (requestResolver.explicitBindingsSet.contains(binding) + || requestResolver.subcomponentDeclarations.containsKey(binding.key())) { return Optional.of(requestResolver); } } @@ -667,17 +753,25 @@ private ImmutableList getResolverLineage() { */ private ImmutableSet getExplicitBindings(Key requestKey) { ImmutableSet.Builder bindings = ImmutableSet.builder(); - Key delegateDeclarationKey = keyFactory.convertToDelegateKey(requestKey); for (Resolver resolver : getResolverLineage()) { - bindings - .addAll(resolver.explicitBindings.get(requestKey)) - .addAll( - createDelegateBindings( - resolver.delegateDeclarations.get(delegateDeclarationKey))); + bindings.addAll(resolver.getLocalExplicitBindings(requestKey)); } return bindings.build(); } + /** + * Returns the explicit {@link ContributionBinding}s that match the {@code requestKey} from + * this resolver. + */ + private ImmutableSet getLocalExplicitBindings(Key requestKey) { + return new ImmutableSet.Builder() + .addAll(explicitBindings.get(requestKey)) + .addAll( + createDelegateBindings( + delegateDeclarations.get(keyFactory.convertToDelegateKey(requestKey)))) + .build(); + } + /** * Returns the explicit multibinding contributions that contribute to the map or set requested * by {@code requestKey} from this and all ancestor resolvers. @@ -711,6 +805,34 @@ private ImmutableSet getMultibindingDeclarations(Key ke return multibindingDeclarations.build(); } + /** + * Returns the {@link SubcomponentDeclaration}s that match the {@code key} from this and all + * ancestor resolvers. + */ + private ImmutableSet getSubcomponentDeclarations(Key key) { + ImmutableSet.Builder subcomponentDeclarations = + ImmutableSet.builder(); + for (Resolver resolver : getResolverLineage()) { + subcomponentDeclarations.addAll(resolver.subcomponentDeclarations.get(key)); + } + return subcomponentDeclarations.build(); + } + /** + * Returns the {@link OptionalBindingDeclaration}s that match the {@code key} from this and + * all ancestor resolvers. + */ + private ImmutableSet getOptionalBindingDeclarations(Key key) { + Optional unwrapped = keyFactory.unwrapOptional(key); + if (!unwrapped.isPresent()) { + return ImmutableSet.of(); + } + ImmutableSet.Builder declarations = ImmutableSet.builder(); + for (Resolver resolver : getResolverLineage()) { + declarations.addAll(resolver.optionalBindingDeclarations.get(unwrapped.get())); + } + return declarations.build(); + } + private Optional getPreviouslyResolvedBindings( final BindingKey bindingKey) { Optional result = Optional.fromNullable(resolvedBindings.get(bindingKey)); @@ -743,13 +865,18 @@ void resolve(BindingKey bindingKey) { * * 2. If there are any explicit bindings in this component, they may conflict with those in * the supercomponent, so resolve them here so that conflicts can be caught. + * + * 3. If the previously resolved binding is an optional binding, and there are any explicit + * bindings for the underlying key in this component, resolve here so that absent + * bindings in the parent can be overridden by present bindings here. */ if (getPreviouslyResolvedBindings(bindingKey).isPresent()) { /* Resolve in the parent in case there are multibinding contributions or conflicts in some * component between this one and the previously-resolved one. */ parentResolver.get().resolve(bindingKey); if (!new MultibindingDependencies().dependsOnLocalMultibindings(bindingKey) - && getExplicitBindings(bindingKey.key()).isEmpty()) { + && getLocalExplicitBindings(bindingKey.key()).isEmpty() + && !hasLocallyPresentOptionalBinding(bindingKey)) { /* Cache the inherited parent component's bindings in case resolving at the parent found * bindings in some component between this one and the previously-resolved one. */ ResolvedBindings inheritedBindings = @@ -805,6 +932,23 @@ ImmutableSet getOwnedModules() { .immutableCopy(); } + /** + * Returns {@code true} if {@code bindingKey} was previously resolved to an optional binding + * for which there is an explicit present binding in this component. + */ + private boolean hasLocallyPresentOptionalBinding(BindingKey bindingKey) { + return Iterables.any( + getPreviouslyResolvedBindings(bindingKey).get().contributionBindings(), + new Predicate() { + @Override + public boolean apply(ContributionBinding binding) { + return binding.bindingKind().equals(SYNTHETIC_OPTIONAL_BINDING) + && !getLocalExplicitBindings(keyFactory.unwrapOptional(binding.key()).get()) + .isEmpty(); + } + }); + } + private final class MultibindingDependencies { private final Set cycleChecker = new HashSet<>(); diff --git a/compiler/src/main/java/dagger/internal/codegen/BindingGraphValidator.java b/compiler/src/main/java/dagger/internal/codegen/BindingGraphValidator.java index df91437de4f..d44498a0516 100644 --- a/compiler/src/main/java/dagger/internal/codegen/BindingGraphValidator.java +++ b/compiler/src/main/java/dagger/internal/codegen/BindingGraphValidator.java @@ -29,20 +29,19 @@ import static com.google.common.base.Verify.verify; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Iterables.indexOf; -import static com.google.common.collect.Maps.filterKeys; import static dagger.internal.codegen.BindingDeclaration.HAS_BINDING_ELEMENT; -import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor.isOfKind; -import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodKind.PRODUCTION_SUBCOMPONENT; -import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodKind.SUBCOMPONENT; +import static dagger.internal.codegen.ConfigurationAnnotations.getComponentAnnotation; import static dagger.internal.codegen.ConfigurationAnnotations.getComponentDependencies; import static dagger.internal.codegen.ContributionBinding.Kind.INJECTION; import static dagger.internal.codegen.ContributionBinding.Kind.SYNTHETIC_MULTIBOUND_MAP; +import static dagger.internal.codegen.ContributionBinding.Kind.SYNTHETIC_OPTIONAL_BINDING; import static dagger.internal.codegen.ContributionBinding.indexMapBindingsByAnnotationType; import static dagger.internal.codegen.ContributionBinding.indexMapBindingsByMapKey; import static dagger.internal.codegen.ContributionType.indexByContributionType; import static dagger.internal.codegen.ErrorMessages.CANNOT_INJECT_WILDCARD_TYPE; import static dagger.internal.codegen.ErrorMessages.CONTAINS_DEPENDENCY_CYCLE_FORMAT; import static dagger.internal.codegen.ErrorMessages.DEPENDS_ON_PRODUCTION_EXECUTOR_FORMAT; +import static dagger.internal.codegen.ErrorMessages.DOUBLE_INDENT; import static dagger.internal.codegen.ErrorMessages.DUPLICATE_BINDINGS_FOR_KEY_FORMAT; import static dagger.internal.codegen.ErrorMessages.DUPLICATE_SIZE_LIMIT; import static dagger.internal.codegen.ErrorMessages.INDENT; @@ -91,7 +90,6 @@ import dagger.internal.codegen.ComponentDescriptor.BuilderSpec; import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor; import dagger.internal.codegen.ContributionBinding.Kind; -import dagger.producers.ProductionComponent; import java.util.ArrayDeque; import java.util.Collection; import java.util.Deque; @@ -197,25 +195,30 @@ boolean hasCycle() { /** * If there is a cycle, the segment of the path that represents the cycle. The first request's - * and the last request's binding keys are equal. The last request is the - * {@linkplain #currentDependencyRequest() current request}. + * and the last request's binding keys are equal. The last request is the {@linkplain + * #currentDependencyRequest() current request}. * * @throws IllegalStateException if {@link #hasCycle()} is {@code false} */ - ImmutableList cycle() { + FluentIterable cycle() { checkState(hasCycle(), "no cycle"); - return FluentIterable.from(path) - .skip(indexOf(keyPath, Predicates.equalTo(currentDependencyRequest().bindingKey()))) - .toList(); + return resolvedRequests() + .skip(indexOf(keyPath, Predicates.equalTo(currentDependencyRequest().bindingKey()))); } /** * Makes {@code request} the current request. Be sure to call {@link #pop()} to back up to the * previous request in the path. */ - void push(ResolvedRequest request) { - path.addLast(request); - keyPath.add(request.dependencyRequest().bindingKey()); + void push(DependencyRequest request, ResolvedBindings resolvedBindings) { + path.add( + ResolvedRequest.create( + request, + resolvedBindings, + path.isEmpty() + ? Optional.absent() + : Optional.of(currentResolvedBindings()))); + keyPath.add(request.bindingKey()); } /** Makes the previous request the current request. */ @@ -235,9 +238,9 @@ int size() { return path.size(); } - /** The dependency requests in this path, starting with the entry point. */ - FluentIterable dependencyRequests() { - return FluentIterable.from(path).transform(ResolvedRequest.DEPENDENCY_REQUEST); + /** Returns the resolved dependency requests in this path, starting with the entry point. */ + FluentIterable resolvedRequests() { + return FluentIterable.from(path); } } @@ -245,12 +248,15 @@ private final class Validation { final BindingGraph subject; final ValidationReport.Builder reportBuilder; final Optional parent; + final ImmutableMap subgraphsByComponentDescriptor; Validation(BindingGraph subject, Optional parent) { this.subject = subject; this.reportBuilder = ValidationReport.about(subject.componentDescriptor().componentDefinitionType()); this.parent = parent; + this.subgraphsByComponentDescriptor = + Maps.uniqueIndex(subject.subgraphs(), BindingGraph.COMPONENT_DESCRIPTOR); } Validation(BindingGraph topLevelGraph) { @@ -280,15 +286,12 @@ void validateSubgraph() { } for (Map.Entry entry : - filterKeys( - subject.componentDescriptor().subcomponents(), - isOfKind(SUBCOMPONENT, PRODUCTION_SUBCOMPONENT)) - .entrySet()) { + subject.componentDescriptor().subcomponentsByFactoryMethod().entrySet()) { validateSubcomponentFactoryMethod( - entry.getKey().methodElement(), entry.getValue().componentDefinitionType()); + entry.getKey().methodElement(), subgraphsByComponentDescriptor.get(entry.getValue())); } - for (BindingGraph subgraph : subject.subgraphs().values()) { + for (BindingGraph subgraph : subject.subgraphs()) { Validation subgraphValidation = new Validation(subgraph, Optional.of(this)); subgraphValidation.validateSubgraph(); reportBuilder.addSubreport(subgraphValidation.buildReport()); @@ -296,8 +299,7 @@ void validateSubgraph() { } private void validateSubcomponentFactoryMethod( - ExecutableElement factoryMethod, TypeElement subcomponentType) { - BindingGraph subgraph = subject.subgraphs().get(factoryMethod); + ExecutableElement factoryMethod, BindingGraph subgraph) { FluentIterable missingModules = FluentIterable.from(subgraph.componentRequirements()) .filter(not(in(subgraphFactoryMethodParameters(factoryMethod)))) @@ -313,7 +315,7 @@ public boolean apply(TypeElement moduleType) { String.format( "%s requires modules which have no visible default constructors. " + "Add the following modules as parameters to this method: %s", - subcomponentType.getQualifiedName(), + subgraph.componentDescriptor().componentDefinitionType().getQualifiedName(), Joiner.on(", ").join(missingModules.toSet())), factoryMethod); } @@ -335,7 +337,7 @@ private ImmutableSet subgraphFactoryMethodParameters( * @param request the current dependency request */ private void traverseDependencyRequest(DependencyRequest request, DependencyPath path) { - path.push(ResolvedRequest.create(request, subject)); + path.push(request, resolvedBindings(request)); try { if (path.hasCycle()) { reportCycle(path); @@ -360,6 +362,14 @@ private void traverseDependencyRequest(DependencyRequest request, DependencyPath } } + private ResolvedBindings resolvedBindings(DependencyRequest request) { + BindingKey bindingKey = request.bindingKey(); + ResolvedBindings resolvedBindings = subject.resolvedBindings().get(bindingKey); + return resolvedBindings == null + ? ResolvedBindings.noBindings(bindingKey, subject.componentDescriptor()) + : resolvedBindings; + } + private Validation validationForComponent(ComponentDescriptor component) { if (component.equals(subject.componentDescriptor())) { return this; @@ -481,12 +491,18 @@ private ResolvedBindings inlineContributionsWithoutBindingElements( ImmutableSetMultimap.builder(); ImmutableSet.Builder multibindingDeclarations = ImmutableSet.builder(); + ImmutableSet.Builder subcomponentDeclarations = + ImmutableSet.builder(); + ImmutableSet.Builder optionalBindingDeclarations = + ImmutableSet.builder(); Queue queue = new ArrayDeque<>(); queue.add(resolvedBinding); for (ResolvedBindings queued = queue.poll(); queued != null; queued = queue.poll()) { multibindingDeclarations.addAll(queued.multibindingDeclarations()); + subcomponentDeclarations.addAll(queued.subcomponentDeclarations()); + optionalBindingDeclarations.addAll(queued.optionalBindingDeclarations()); for (Map.Entry bindingEntry : queued.allContributionBindings().entries()) { BindingGraph owningGraph = validationForComponent(bindingEntry.getKey()).subject; @@ -504,7 +520,9 @@ private ResolvedBindings inlineContributionsWithoutBindingElements( resolvedBinding.bindingKey(), resolvedBinding.owningComponent(), contributions.build(), - multibindingDeclarations.build()); + multibindingDeclarations.build(), + subcomponentDeclarations.build(), + optionalBindingDeclarations.build()); } private ImmutableListMultimap declarationsByType( @@ -709,10 +727,9 @@ private void validateComponentHierarchy( message.toString(), compilerOptions.scopeCycleValidationType().diagnosticKind().get(), rootComponent, - getAnnotationMirror(rootComponent, Component.class).get()); + getComponentAnnotation(rootComponent).get()); } else { - Optional componentAnnotation = - getAnnotationMirror(componentType, Component.class); + Optional componentAnnotation = getComponentAnnotation(componentType); if (componentAnnotation.isPresent()) { componentStack.push(componentType); @@ -860,9 +877,7 @@ private void validateScopeHierarchy(TypeElement rootComponent, message.toString(), compilerOptions.scopeCycleValidationType().diagnosticKind().get(), rootComponent, - getAnnotationMirror(rootComponent, Component.class) - .or(getAnnotationMirror(rootComponent, ProductionComponent.class)) - .get()); + getComponentAnnotation(rootComponent).get()); } scopedDependencyStack.pop(); } else { @@ -964,14 +979,31 @@ private void reportProviderMayNotDependOnProducer(DependencyPath path) { PROVIDER_ENTRY_POINT_MAY_NOT_DEPEND_ON_PRODUCER_FORMAT, formatCurrentDependencyRequestKey(path)); } else { - ImmutableSet dependentProvisions = + FluentIterable dependentProvisions = provisionsDependingOnLatestRequest(path); - // TODO(beder): Consider displaying all dependent provisions in the error message. If we do - // that, should we display all productions that depend on them also? - new Formatter(errorMessage) - .format( - PROVIDER_MAY_NOT_DEPEND_ON_PRODUCER_FORMAT, - dependentProvisions.iterator().next().key()); + if (dependentProvisions + .transform(ContributionBinding.KIND) + .contains(SYNTHETIC_OPTIONAL_BINDING)) { + // TODO(dpb): Implement @BindsOptionalOf for producers. + errorMessage + .append("Using optional bindings with @Produces bindings is not yet supported.\n") + .append(INDENT) + .append(formatCurrentDependencyRequestKey(path)) + .append(" is produced at\n") + .append(DOUBLE_INDENT) + .append( + bindingDeclarationFormatter.format( + path.currentResolvedBindings().contributionBinding())) + .append('\n') + .append(dependencyRequestFormatter.toDependencyTrace(path)); + } else { + // TODO(beder): Consider displaying all dependent provisions in the error message. If we + // do that, should we display all productions that depend on them also? + new Formatter(errorMessage) + .format( + PROVIDER_MAY_NOT_DEPEND_ON_PRODUCER_FORMAT, + dependentProvisions.iterator().next().key()); + } } reportBuilder.addError(errorMessage.toString(), path.entryPointElement()); } @@ -1048,10 +1080,12 @@ private void reportDuplicateBindings(DependencyPath path) { StringBuilder builder = new StringBuilder(); new Formatter(builder) .format(DUPLICATE_BINDINGS_FOR_KEY_FORMAT, formatCurrentDependencyRequestKey(path)); - ImmutableSet duplicateBindings = - inlineContributionsWithoutBindingElements(resolvedBindings).contributionBindings(); + ResolvedBindings inlined = inlineContributionsWithoutBindingElements(resolvedBindings); + ImmutableSet duplicateBindings = inlined.contributionBindings(); + Set conflictingDeclarations = + Sets.union(duplicateBindings, inlined.subcomponentDeclarations()); bindingDeclarationFormatter.formatIndentedList( - builder, duplicateBindings, 1, DUPLICATE_SIZE_LIMIT); + builder, conflictingDeclarations, 1, DUPLICATE_SIZE_LIMIT); owningReportBuilder(duplicateBindings).addError(builder.toString(), path.entryPointElement()); } @@ -1149,7 +1183,7 @@ private void reportInconsistentMapKeyAnnotations( } private void reportCycle(DependencyPath path) { - if (!providersBreakingCycle(path.cycle()).isEmpty()) { + if (!providersBreakingCycle(path).isEmpty()) { return; } // TODO(cgruber): Provide a hint for the start and end of the cycle. @@ -1166,40 +1200,62 @@ private void reportCycle(DependencyPath path) { } /** - * Returns any steps in a dependency cycle that "break" the cycle. These are any - * {@link Provider}, {@link Lazy}, or {@code Map>} requests after the first - * request in the cycle. + * Returns any steps in a dependency cycle that "break" the cycle. These are any {@link + * Provider}, {@link Lazy}, or {@code Map>} requests after the first request in + * the cycle. * *

If an implicit {@link Provider} dependency on {@code Map>} is immediately * preceded by a dependency on {@code Map}, which means that the map's {@link Provider}s' * {@link Provider#get() get()} methods are called during provision and so the cycle is not * really broken. + * + *

A request for an instance of {@code Optional} breaks the cycle if a request for the {@code + * Optional}'s type parameter would. */ - private ImmutableSet providersBreakingCycle( - ImmutableList cycle) { - return FluentIterable.from(cycle) + private ImmutableSet providersBreakingCycle(DependencyPath path) { + return path.cycle() .skip(1) - .transform(ResolvedRequest.DEPENDENCY_REQUEST) - .filter(DependencyRequest.HAS_REQUEST_ELEMENT) .filter( - new Predicate() { + new Predicate() { @Override - public boolean apply(DependencyRequest dependencyRequest) { - switch (dependencyRequest.kind()) { + public boolean apply(ResolvedRequest resolvedRequest) { + DependencyRequest dependencyRequest = resolvedRequest.dependencyRequest(); + if (dependencyRequest.requestElement().isPresent()) { + // Non-synthetic request + return breaksCycle(dependencyRequest.key().type(), dependencyRequest.kind()); + } else if (!resolvedRequest.dependentOptionalBindingDeclarations().isEmpty()) { + // Synthetic request from a @BindsOptionalOf: test the type inside the Optional. + // Optional breaks the cycle. + TypeMirror requestedOptionalType = + resolvedRequest.dependentBindings().get().key().type(); + DependencyRequest.KindAndType kindAndType = + DependencyRequest.extractKindAndType( + OptionalType.from(requestedOptionalType).valueType()); + return breaksCycle(kindAndType.type(), kindAndType.kind()); + } else { + // Other synthetic requests. + return false; + } + } + + private boolean breaksCycle( + TypeMirror requestedType, DependencyRequest.Kind requestKind) { + switch (requestKind) { case PROVIDER: case LAZY: case PROVIDER_OF_LAZY: return true; case INSTANCE: - return MapType.isMap(dependencyRequest.key()) - && MapType.from(dependencyRequest.key()).valuesAreTypeOf(Provider.class); + return MapType.isMap(requestedType) + && MapType.from(requestedType).valuesAreTypeOf(Provider.class); default: return false; } } }) + .transform(ResolvedRequest.DEPENDENCY_REQUEST) .toSet(); } } @@ -1266,7 +1322,7 @@ private boolean doesPathRequireProvisionOnly(DependencyPath path) { * Returns any provision bindings resolved for the second-most-recent request in the given path; * that is, returns those provision bindings that depend on the latest request in the path. */ - private ImmutableSet provisionsDependingOnLatestRequest( + private FluentIterable provisionsDependingOnLatestRequest( final DependencyPath path) { return FluentIterable.from(path.previousResolvedBindings().bindings()) .filter(BindingType.isOfType(BindingType.PROVISION)) @@ -1277,7 +1333,7 @@ public boolean apply(Binding binding) { return binding.implicitDependencies().contains(path.currentDependencyRequest()); } }) - .toSet(); + .filter(ContributionBinding.class); } private String formatContributionType(ContributionType type) { @@ -1304,15 +1360,36 @@ abstract static class ResolvedRequest { abstract DependencyRequest dependencyRequest(); abstract ResolvedBindings resolvedBindings(); + + /** + * The {@link #resolvedBindings()} of the previous entry in the {@link DependencyPath}. One of + * these bindings depends directly on {@link #dependencyRequest()}. + */ + abstract Optional dependentBindings(); - static ResolvedRequest create(DependencyRequest request, BindingGraph graph) { - BindingKey bindingKey = request.bindingKey(); - ResolvedBindings resolvedBindings = graph.resolvedBindings().get(bindingKey); + /** + * If the binding that depends on {@link #dependencyRequest()} is a synthetic optional binding, + * returns its {@code @BindsOptionalOf} methods. + */ + ImmutableSet dependentOptionalBindingDeclarations() { + if (dependentBindings().isPresent()) { + ResolvedBindings dependentBindings = dependentBindings().get(); + for (ContributionBinding dependentBinding : dependentBindings.contributionBindings()) { + if (dependentBinding.bindingKind().equals(SYNTHETIC_OPTIONAL_BINDING) + && dependentBinding.dependencies().contains(dependencyRequest())) { + return dependentBindings.optionalBindingDeclarations(); + } + } + } + return ImmutableSet.of(); + } + + private static ResolvedRequest create( + DependencyRequest request, + ResolvedBindings resolvedBindings, + Optional dependentBindings) { return new AutoValue_BindingGraphValidator_ResolvedRequest( - request, - resolvedBindings == null - ? ResolvedBindings.noBindings(bindingKey, graph.componentDescriptor()) - : resolvedBindings); + request, resolvedBindings, dependentBindings); } static final Function DEPENDENCY_REQUEST = diff --git a/compiler/src/main/java/dagger/internal/codegen/BindingMethodValidator.java b/compiler/src/main/java/dagger/internal/codegen/BindingMethodValidator.java index 6026e6ed125..446262f68f7 100644 --- a/compiler/src/main/java/dagger/internal/codegen/BindingMethodValidator.java +++ b/compiler/src/main/java/dagger/internal/codegen/BindingMethodValidator.java @@ -62,6 +62,7 @@ import java.lang.annotation.Annotation; import javax.annotation.OverridingMethodsMustInvokeSuper; import javax.annotation.processing.Messager; +import javax.inject.Qualifier; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeKind; @@ -75,7 +76,7 @@ abstract class BindingMethodValidator { private final Elements elements; private final Types types; private final Class methodAnnotation; - private final ImmutableSet> enclosingElementAnnotations; + private final ImmutableSet> enclosingElementAnnotations; private final Abstractness abstractness; private final ExceptionSuperclass exceptionSuperclass; private final LoadingCache> cache = @@ -90,6 +91,7 @@ public ValidationReport load(ExecutableElement method) { return builder.build(); } }); + private final AllowsMultibindings allowsMultibindings; /** * Creates a validator object. @@ -104,14 +106,16 @@ protected BindingMethodValidator( Class methodAnnotation, Class enclosingElementAnnotation, Abstractness abstractness, - ExceptionSuperclass exceptionSuperclass) { + ExceptionSuperclass exceptionSuperclass, + AllowsMultibindings allowsMultibindings) { this( elements, types, methodAnnotation, ImmutableSet.of(enclosingElementAnnotation), abstractness, - exceptionSuperclass); + exceptionSuperclass, + allowsMultibindings); } /** @@ -127,14 +131,15 @@ protected BindingMethodValidator( Class methodAnnotation, Iterable> enclosingElementAnnotations, Abstractness abstractness, - ExceptionSuperclass exceptionSuperclass) { + ExceptionSuperclass exceptionSuperclass, + AllowsMultibindings allowsMultibindings) { this.elements = elements; this.types = types; this.methodAnnotation = methodAnnotation; - this.enclosingElementAnnotations = - ImmutableSet.>copyOf(enclosingElementAnnotations); + this.enclosingElementAnnotations = ImmutableSet.copyOf(enclosingElementAnnotations); this.abstractness = abstractness; this.exceptionSuperclass = exceptionSuperclass; + this.allowsMultibindings = allowsMultibindings; } /** The annotation that identifies methods validated by this object. */ @@ -337,6 +342,9 @@ protected void checkQualifiers(ValidationReport.Builder build * {@code MAP} has any. */ protected void checkMapKeys(ValidationReport.Builder builder) { + if (!allowsMultibindings.allowsMultibindings()) { + return; + } ImmutableSet mapKeys = getMapKeys(builder.getSubject()); if (ContributionType.fromBindingMethod(builder.getSubject()).equals(ContributionType.MAP)) { switch (mapKeys.size()) { @@ -360,6 +368,9 @@ protected void checkMapKeys(ValidationReport.Builder builder) * annotation has a {@code type} parameter. */ protected void checkMultibindings(ValidationReport.Builder builder) { + if (!allowsMultibindings.allowsMultibindings()) { + return; + } ImmutableSet multibindingAnnotations = MultibindingAnnotations.forMethod(builder.getSubject()); if (multibindingAnnotations.size() > 1) { @@ -473,4 +484,22 @@ protected void checkThrows( } } } + + /** Whether to check multibinding annotations. */ + protected enum AllowsMultibindings { + /** + * This method disallows multibinding annotations, so don't bother checking for their validity. + * {@link MultibindingAnnotationsProcessingStep} will add errors if the method has any + * multibinding annotations. + */ + NO_MULTIBINDINGS, + + /** This method allows multibinding annotations, so validate them. */ + ALLOWS_MULTIBINDINGS, + ; + + private boolean allowsMultibindings() { + return this == ALLOWS_MULTIBINDINGS; + } + } } diff --git a/compiler/src/main/java/dagger/internal/codegen/BindingVariableNamer.java b/compiler/src/main/java/dagger/internal/codegen/BindingVariableNamer.java index 70df6d483e0..36db21ab6ae 100644 --- a/compiler/src/main/java/dagger/internal/codegen/BindingVariableNamer.java +++ b/compiler/src/main/java/dagger/internal/codegen/BindingVariableNamer.java @@ -18,8 +18,10 @@ import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; +import static dagger.internal.codegen.ConfigurationAnnotations.isSubcomponentBuilder; import java.util.Iterator; +import javax.lang.model.element.Element; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleTypeVisitor6; @@ -48,9 +50,16 @@ static String name(Binding binding) { type.accept( new SimpleTypeVisitor6() { @Override - public Void visitDeclared(DeclaredType t, StringBuilder builder) { - builder.append(t.asElement().getSimpleName()); - Iterator argumentIterator = t.getTypeArguments().iterator(); + public Void visitDeclared(DeclaredType declaredType, StringBuilder builder) { + Element element = declaredType.asElement(); + if (isSubcomponentBuilder(element)) { + // Most Subcomponent builders are named "Builder", so add their associated + // Subcomponent type so that they're not all "builderProvider{N}" + builder.append(element.getEnclosingElement().getSimpleName()); + } + builder.append(element.getSimpleName()); + Iterator argumentIterator = + declaredType.getTypeArguments().iterator(); if (argumentIterator.hasNext()) { builder.append("Of"); TypeMirror first = argumentIterator.next(); diff --git a/compiler/src/main/java/dagger/internal/codegen/BindsMethodValidator.java b/compiler/src/main/java/dagger/internal/codegen/BindsMethodValidator.java index 73e5d8f8e6a..3108e8a259a 100644 --- a/compiler/src/main/java/dagger/internal/codegen/BindsMethodValidator.java +++ b/compiler/src/main/java/dagger/internal/codegen/BindsMethodValidator.java @@ -18,6 +18,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.BindingMethodValidator.Abstractness.MUST_BE_ABSTRACT; +import static dagger.internal.codegen.BindingMethodValidator.AllowsMultibindings.ALLOWS_MULTIBINDINGS; import static dagger.internal.codegen.BindingMethodValidator.ExceptionSuperclass.RUNTIME_EXCEPTION; import static dagger.internal.codegen.ErrorMessages.BINDS_ELEMENTS_INTO_SET_METHOD_RETURN_SET; import static dagger.internal.codegen.ErrorMessages.BINDS_METHOD_ONE_ASSIGNABLE_PARAMETER; @@ -55,7 +56,8 @@ final class BindsMethodValidator extends BindingMethodValidator { Binds.class, ImmutableSet.of(Module.class, ProducerModule.class), MUST_BE_ABSTRACT, - RUNTIME_EXCEPTION); + RUNTIME_EXCEPTION, + ALLOWS_MULTIBINDINGS); this.types = types; this.elements = elements; } diff --git a/compiler/src/main/java/dagger/internal/codegen/BindsOptionalOfMethodValidator.java b/compiler/src/main/java/dagger/internal/codegen/BindsOptionalOfMethodValidator.java new file mode 100644 index 00000000000..e00ee997465 --- /dev/null +++ b/compiler/src/main/java/dagger/internal/codegen/BindsOptionalOfMethodValidator.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 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 dagger.internal.codegen.BindingMethodValidator.Abstractness.MUST_BE_ABSTRACT; +import static dagger.internal.codegen.BindingMethodValidator.AllowsMultibindings.NO_MULTIBINDINGS; +import static dagger.internal.codegen.BindingMethodValidator.ExceptionSuperclass.NO_EXCEPTIONS; +import static dagger.internal.codegen.ErrorMessages.BINDS_OPTIONAL_OF_METHOD_HAS_PARAMETER; +import static dagger.internal.codegen.ErrorMessages.BINDS_OPTIONAL_OF_METHOD_RETURNS_IMPLICITLY_PROVIDED_TYPE; +import static dagger.internal.codegen.InjectionAnnotations.getQualifiers; +import static dagger.internal.codegen.InjectionAnnotations.injectedConstructors; +import static dagger.internal.codegen.Key.isValidImplicitProvisionKey; + +import com.google.auto.common.MoreElements; +import com.google.auto.common.MoreTypes; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableSet; +import dagger.BindsOptionalOf; +import dagger.Module; +import dagger.producers.ProducerModule; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +/** A validator for {@link BindsOptionalOf} methods. */ +final class BindsOptionalOfMethodValidator extends BindingMethodValidator { + + private final Types types; + + BindsOptionalOfMethodValidator(Elements elements, Types types) { + super( + elements, + types, + BindsOptionalOf.class, + ImmutableSet.of(Module.class, ProducerModule.class), + MUST_BE_ABSTRACT, + NO_EXCEPTIONS, + NO_MULTIBINDINGS); + this.types = types; + } + + @Override + protected void checkMethod(ValidationReport.Builder builder) { + super.checkMethod(builder); + checkParameters(builder); + } + + @Override + protected void checkKeyType( + ValidationReport.Builder builder, TypeMirror keyType) { + super.checkKeyType(builder, keyType); + if (isValidImplicitProvisionKey( + FluentIterable.from(getQualifiers(builder.getSubject())).first(), keyType, types) + && !injectedConstructors(MoreElements.asType(MoreTypes.asDeclared(keyType).asElement())) + .isEmpty()) { + builder.addError(BINDS_OPTIONAL_OF_METHOD_RETURNS_IMPLICITLY_PROVIDED_TYPE); + } + } + + private void checkParameters(ValidationReport.Builder builder) { + if (!builder.getSubject().getParameters().isEmpty()) { + builder.addError(BINDS_OPTIONAL_OF_METHOD_HAS_PARAMETER); + } + } +} diff --git a/compiler/src/main/java/dagger/internal/codegen/ComponentDescriptor.java b/compiler/src/main/java/dagger/internal/codegen/ComponentDescriptor.java index 401c647513d..7b649b1d977 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ComponentDescriptor.java +++ b/compiler/src/main/java/dagger/internal/codegen/ComponentDescriptor.java @@ -24,6 +24,8 @@ import static dagger.internal.codegen.ConfigurationAnnotations.enclosedBuilders; import static dagger.internal.codegen.ConfigurationAnnotations.getComponentDependencies; import static dagger.internal.codegen.ConfigurationAnnotations.getComponentModules; +import static dagger.internal.codegen.ConfigurationAnnotations.isSubcomponent; +import static dagger.internal.codegen.ConfigurationAnnotations.isSubcomponentBuilder; import static dagger.internal.codegen.InjectionAnnotations.getQualifier; import static javax.lang.model.type.TypeKind.DECLARED; import static javax.lang.model.type.TypeKind.VOID; @@ -35,12 +37,12 @@ import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; -import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.squareup.javapoet.ClassName; import dagger.Component; import dagger.Lazy; @@ -57,6 +59,7 @@ import java.util.Set; import javax.inject.Provider; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; @@ -240,13 +243,7 @@ static Function> toBuilderAnnotationType() { *

Note that for subcomponents this will not include descriptors for any modules that * are declared in parent components. */ - ImmutableSet transitiveModules() { - Set transitiveModules = new LinkedHashSet<>(); - for (ModuleDescriptor module : modules()) { - addTransitiveModules(transitiveModules, module); - } - return ImmutableSet.copyOf(transitiveModules); - } + abstract ImmutableSet transitiveModules(); ImmutableSet transitiveModuleTypes() { return FluentIterable.from(transitiveModules()) @@ -254,15 +251,22 @@ ImmutableSet transitiveModuleTypes() { .toSet(); } - @CanIgnoreReturnValue - private static Set addTransitiveModules( + private static ImmutableSet transitiveModules( + Iterable topLevelModules) { + Set transitiveModules = new LinkedHashSet<>(); + for (ModuleDescriptor module : topLevelModules) { + addTransitiveModules(transitiveModules, module); + } + return ImmutableSet.copyOf(transitiveModules); + } + + private static void addTransitiveModules( Set transitiveModules, ModuleDescriptor module) { if (transitiveModules.add(module)) { for (ModuleDescriptor includedModule : module.includedModules()) { addTransitiveModules(transitiveModules, includedModule); } } - return transitiveModules; } /** @@ -277,7 +281,73 @@ private static Set addTransitiveModules( */ abstract ImmutableSet scopes(); - abstract ImmutableMap subcomponents(); + /** + * All {@link Subcomponent}s which are direct children of this component. This includes + * subcomponents installed from {@link Module#subcomponents()} as well as subcomponent {@linkplain + * #subcomponentsByFactoryMethod() factory methods} and {@linkplain + * #subcomponentsByBuilderMethod() builder methods}. + */ + ImmutableSet subcomponents() { + return ImmutableSet.builder() + .addAll(subcomponentsByFactoryMethod().values()) + .addAll(subcomponentsByBuilderMethod().values()) + .addAll(subcomponentsFromModules()) + .build(); + } + + /** + * All {@linkplain Subcomponent direct child} components that are declared by a {@linkplain + * Module#subcomponents() module's subcomponents}. + */ + abstract ImmutableSet subcomponentsFromModules(); + + /** + * All {@linkplain Subcomponent direct child} components that are declared by a subcomponent + * factory method. + */ + abstract ImmutableBiMap + subcomponentsByFactoryMethod(); + + /** + * All {@linkplain Subcomponent direct child} components that are declared by a subcomponent + * builder method. + */ + abstract ImmutableBiMap + subcomponentsByBuilderMethod(); + + /** + * All {@linkplain Subcomponent direct child} components that are declared by an entry point + * method. This is equivalent to the set of values from {@link #subcomponentsByFactoryMethod()} + * and {@link #subcomponentsByBuilderMethod(). + */ + ImmutableSet subcomponentsFromEntryPoints() { + return ImmutableSet.builder() + .addAll(subcomponentsByFactoryMethod().values()) + .addAll(subcomponentsByBuilderMethod().values()) + .build(); + } + + // TODO(ronshapiro): convert this to use @Memoized + private ImmutableBiMap subcomponentsByBuilderType; + + ImmutableBiMap subcomponentsByBuilderType() { + if (subcomponentsByBuilderType == null) { + subcomponentsByBuilderType = computeSubcomponentsByBuilderType(); + } + return subcomponentsByBuilderType; + } + + private ImmutableBiMap computeSubcomponentsByBuilderType() { + ImmutableBiMap.Builder subcomponentsByBuilderType = + ImmutableBiMap.builder(); + for (ComponentDescriptor subcomponent : subcomponents()) { + if (subcomponent.builderSpec().isPresent()) { + subcomponentsByBuilderType.put( + subcomponent.builderSpec().get().builderDefinitionType(), subcomponent); + } + } + return subcomponentsByBuilderType.build(); + } abstract ImmutableSet componentMethods(); @@ -327,6 +397,13 @@ static ComponentMethodDescriptor forSubcomponent( ComponentMethodKind kind, ExecutableElement methodElement) { return create(kind, Optional.absent(), methodElement); } + + static ComponentMethodDescriptor forSubcomponentBuilder( + ComponentMethodKind kind, + DependencyRequest dependencyRequestForBuilder, + ExecutableElement methodElement) { + return create(kind, Optional.of(dependencyRequestForBuilder), methodElement); + } } enum ComponentMethodKind { @@ -338,6 +415,10 @@ enum ComponentMethodKind { PRODUCTION_SUBCOMPONENT, PRODUCTION_SUBCOMPONENT_BUILDER; + boolean isSubcomponentKind() { + return this == SUBCOMPONENT || this == PRODUCTION_SUBCOMPONENT; + } + /** * Returns the component kind associated with this component method, if it exists. Otherwise, * throws. @@ -417,27 +498,40 @@ private ComponentDescriptor create( } } - ImmutableSet.Builder modules = ImmutableSet.builder(); - for (TypeMirror moduleIncludesType : getComponentModules(componentMirror)) { - modules.add(moduleDescriptorFactory.create(MoreTypes.asTypeElement(moduleIncludesType))); + ImmutableSet.Builder modulesBuilder = ImmutableSet.builder(); + for (TypeMirror componentModulesType : getComponentModules(componentMirror)) { + modulesBuilder.add( + moduleDescriptorFactory.create(MoreTypes.asTypeElement(componentModulesType))); } if (kind.equals(Kind.PRODUCTION_COMPONENT) || (kind.equals(Kind.PRODUCTION_SUBCOMPONENT) && parentKind.isPresent() && (parentKind.get().equals(Kind.COMPONENT) || parentKind.get().equals(Kind.SUBCOMPONENT)))) { - modules.add(descriptorForMonitoringModule(componentDefinitionType)); - modules.add(descriptorForProductionExecutorModule(componentDefinitionType)); + modulesBuilder.add(descriptorForMonitoringModule(componentDefinitionType)); + modulesBuilder.add(descriptorForProductionExecutorModule(componentDefinitionType)); + } + ImmutableSet modules = modulesBuilder.build(); + ImmutableSet transitiveModules = transitiveModules(modules); + ImmutableSet.Builder subcomponentsFromModules = ImmutableSet.builder(); + for (ModuleDescriptor module : transitiveModules) { + for (SubcomponentDeclaration subcomponentDeclaration : module.subcomponentDeclarations()) { + TypeElement subcomponent = subcomponentDeclaration.subcomponentType(); + subcomponentsFromModules.add( + create( + subcomponent, Kind.forAnnotatedElement(subcomponent).get(), Optional.of(kind))); + } } - ImmutableSet unimplementedMethods = Util.getUnimplementedMethods(elements, componentDefinitionType); ImmutableSet.Builder componentMethodsBuilder = ImmutableSet.builder(); - ImmutableMap.Builder subcomponentDescriptors = - ImmutableMap.builder(); + ImmutableBiMap.Builder + subcomponentsByFactoryMethod = ImmutableBiMap.builder(); + ImmutableBiMap.Builder + subcomponentsByBuilderMethod = ImmutableBiMap.builder(); for (ExecutableElement componentMethod : unimplementedMethods) { ExecutableType resolvedMethod = MoreTypes.asExecutable(types.asMemberOf(declaredComponentType, componentMethod)); @@ -447,7 +541,7 @@ private ComponentDescriptor create( switch (componentMethodDescriptor.kind()) { case SUBCOMPONENT: case PRODUCTION_SUBCOMPONENT: - subcomponentDescriptors.put( + subcomponentsByFactoryMethod.put( componentMethodDescriptor, create( MoreElements.asType(MoreTypes.asElement(resolvedMethod.getReturnType())), @@ -456,7 +550,7 @@ private ComponentDescriptor create( break; case SUBCOMPONENT_BUILDER: case PRODUCTION_SUBCOMPONENT_BUILDER: - subcomponentDescriptors.put( + subcomponentsByBuilderMethod.put( componentMethodDescriptor, create( MoreElements.asType( @@ -485,10 +579,13 @@ private ComponentDescriptor create( componentMirror, componentDefinitionType, componentDependencyTypes, - modules.build(), + modules, + transitiveModules, dependencyMethodIndex.build(), scopes, - subcomponentDescriptors.build(), + subcomponentsFromModules.build(), + subcomponentsByFactoryMethod.build(), + subcomponentsByBuilderMethod.build(), componentMethodsBuilder.build(), builderSpec); } @@ -512,21 +609,23 @@ private ComponentMethodDescriptor getDescriptorForComponentMethod( dependencyRequestFactory.forComponentMembersInjectionMethod( componentMethod, resolvedComponentMethod)); } else if (!getQualifier(componentMethod).isPresent()) { - if (isAnnotationPresent(MoreTypes.asElement(returnType), Subcomponent.class)) { - return ComponentMethodDescriptor.forSubcomponent( - ComponentMethodKind.SUBCOMPONENT, componentMethod); - } else if (isAnnotationPresent( - MoreTypes.asElement(returnType), ProductionSubcomponent.class)) { + Element returnTypeElement = MoreTypes.asElement(returnType); + if (isSubcomponent(returnTypeElement)) { return ComponentMethodDescriptor.forSubcomponent( - ComponentMethodKind.PRODUCTION_SUBCOMPONENT, componentMethod); - } else if (isAnnotationPresent( - MoreTypes.asElement(returnType), Subcomponent.Builder.class)) { - return ComponentMethodDescriptor.forSubcomponent( - ComponentMethodKind.SUBCOMPONENT_BUILDER, componentMethod); - } else if (isAnnotationPresent( - MoreTypes.asElement(returnType), ProductionSubcomponent.Builder.class)) { - return ComponentMethodDescriptor.forSubcomponent( - ComponentMethodKind.PRODUCTION_SUBCOMPONENT_BUILDER, componentMethod); + isAnnotationPresent(returnTypeElement, Subcomponent.class) + ? ComponentMethodKind.SUBCOMPONENT + : ComponentMethodKind.PRODUCTION_SUBCOMPONENT, + componentMethod); + } else if (isSubcomponentBuilder(returnTypeElement)) { + DependencyRequest dependencyRequest = + dependencyRequestFactory.forComponentProvisionMethod( + componentMethod, resolvedComponentMethod); + return ComponentMethodDescriptor.forSubcomponentBuilder( + isAnnotationPresent(returnTypeElement, Subcomponent.Builder.class) + ? ComponentMethodKind.SUBCOMPONENT_BUILDER + : ComponentMethodKind.PRODUCTION_SUBCOMPONENT_BUILDER, + dependencyRequest, + componentMethod); } } } diff --git a/compiler/src/main/java/dagger/internal/codegen/ComponentHierarchyValidator.java b/compiler/src/main/java/dagger/internal/codegen/ComponentHierarchyValidator.java index 217c5008b57..91c9f5a7321 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ComponentHierarchyValidator.java +++ b/compiler/src/main/java/dagger/internal/codegen/ComponentHierarchyValidator.java @@ -45,40 +45,24 @@ private ValidationReport validateSubcomponentMethods( ValidationReport.Builder reportBuilder = ValidationReport.about(componentDescriptor.componentDefinitionType()); for (Map.Entry subcomponentEntry : - componentDescriptor.subcomponents().entrySet()) { + componentDescriptor.subcomponentsByFactoryMethod().entrySet()) { ComponentMethodDescriptor subcomponentMethodDescriptor = subcomponentEntry.getKey(); ComponentDescriptor subcomponentDescriptor = subcomponentEntry.getValue(); // validate the way that we create subcomponents - switch (subcomponentMethodDescriptor.kind()) { - case SUBCOMPONENT: - case PRODUCTION_SUBCOMPONENT: - for (VariableElement factoryMethodParameter : - subcomponentMethodDescriptor.methodElement().getParameters()) { - TypeElement moduleType = MoreTypes.asTypeElement(factoryMethodParameter.asType()); - TypeElement originatingComponent = existingModuleToOwners.get(moduleType); - if (originatingComponent != null) { - /* Factory method tries to pass a module that is already present in the parent. - * This is an error. */ - reportBuilder.addError( - String.format( - "%s is present in %s. A subcomponent cannot use an instance of a " - + "module that differs from its parent.", - moduleType.getSimpleName(), - originatingComponent.getQualifiedName()), - factoryMethodParameter); - } - } - break; - - case SUBCOMPONENT_BUILDER: - case PRODUCTION_SUBCOMPONENT_BUILDER: - /* A subcomponent builder allows you to pass a module that is already present in the - * parent. This can't be an error because it might be valid in _other_ components. Don't - * bother warning, because there's nothing to do except suppress the warning. */ - break; - - default: - throw new AssertionError(); + for (VariableElement factoryMethodParameter : + subcomponentMethodDescriptor.methodElement().getParameters()) { + TypeElement moduleType = MoreTypes.asTypeElement(factoryMethodParameter.asType()); + TypeElement originatingComponent = existingModuleToOwners.get(moduleType); + if (originatingComponent != null) { + /* Factory method tries to pass a module that is already present in the parent. + * This is an error. */ + reportBuilder.addError( + String.format( + "%s is present in %s. A subcomponent cannot use an instance of a " + + "module that differs from its parent.", + moduleType.getSimpleName(), originatingComponent.getQualifiedName()), + factoryMethodParameter); + } } reportBuilder.addSubreport( validateSubcomponentMethods( diff --git a/compiler/src/main/java/dagger/internal/codegen/ComponentProcessor.java b/compiler/src/main/java/dagger/internal/codegen/ComponentProcessor.java index be36c9bfb3a..9ca87cf2fe1 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ComponentProcessor.java +++ b/compiler/src/main/java/dagger/internal/codegen/ComponentProcessor.java @@ -90,6 +90,8 @@ protected Iterable initSteps() { new MultibindsMethodValidator(elements, types); MultibindingsMethodValidator multibindingsMethodValidator = new MultibindingsMethodValidator(elements, types); + BindsOptionalOfMethodValidator bindsOptionalOfMethodValidator = + new BindsOptionalOfMethodValidator(elements, types); Key.Factory keyFactory = new Key.Factory(types, elements); @@ -122,12 +124,16 @@ protected Iterable initSteps() { new ProductionBinding.Factory(types, keyFactory, dependencyRequestFactory); MultibindingDeclaration.Factory multibindingDeclarationFactory = new MultibindingDeclaration.Factory(elements, types, keyFactory); + SubcomponentDeclaration.Factory subcomponentDeclarationFactory = + new SubcomponentDeclaration.Factory(keyFactory); MembersInjectionBinding.Factory membersInjectionBindingFactory = new MembersInjectionBinding.Factory(elements, types, keyFactory, dependencyRequestFactory); DelegateDeclaration.Factory bindingDelegateDeclarationFactory = new DelegateDeclaration.Factory(types, keyFactory, dependencyRequestFactory); + OptionalBindingDeclaration.Factory optionalBindingDeclarationFactory = + new OptionalBindingDeclaration.Factory(keyFactory); this.injectBindingRegistry = new InjectBindingRegistry( @@ -145,7 +151,9 @@ protected Iterable initSteps() { provisionBindingFactory, productionBindingFactory, multibindingDeclarationFactory, - bindingDelegateDeclarationFactory); + bindingDelegateDeclarationFactory, + subcomponentDeclarationFactory, + optionalBindingDeclarationFactory); ComponentDescriptor.Factory componentDescriptorFactory = new ComponentDescriptor.Factory( elements, types, dependencyRequestFactory, moduleDescriptorFactory); @@ -158,7 +166,10 @@ protected Iterable initSteps() { provisionBindingFactory, productionBindingFactory); - MapKeyGenerator mapKeyGenerator = new MapKeyGenerator(filer, elements); + AnnotationCreatorGenerator annotationCreatorGenerator = + new AnnotationCreatorGenerator(filer, elements); + UnwrappedMapKeyGenerator unwrappedMapKeyGenerator = + new UnwrappedMapKeyGenerator(filer, elements); ComponentHierarchyValidator componentHierarchyValidator = new ComponentHierarchyValidator(); BindingGraphValidator bindingGraphValidator = new BindingGraphValidator( @@ -174,7 +185,8 @@ protected Iterable initSteps() { keyFactory); return ImmutableList.of( - new MapKeyProcessingStep(messager, types, mapKeyValidator, mapKeyGenerator), + new MapKeyProcessingStep( + messager, types, mapKeyValidator, annotationCreatorGenerator, unwrappedMapKeyGenerator), new InjectProcessingStep(injectBindingRegistry), new MonitoringModuleProcessingStep(messager, monitoringModuleGenerator), new ProductionExecutorModuleProcessingStep(messager, productionExecutorModuleGenerator), @@ -187,7 +199,8 @@ protected Iterable initSteps() { factoryGenerator, providesMethodValidator, bindsMethodValidator, - multibindsMethodValidator), + multibindsMethodValidator, + bindsOptionalOfMethodValidator), new ComponentProcessingStep( ComponentDescriptor.Kind.COMPONENT, messager, @@ -209,7 +222,8 @@ protected Iterable initSteps() { producerFactoryGenerator, producesMethodValidator, bindsMethodValidator, - multibindsMethodValidator), + multibindsMethodValidator, + bindsOptionalOfMethodValidator), new ComponentProcessingStep( ComponentDescriptor.Kind.PRODUCTION_COMPONENT, messager, diff --git a/compiler/src/main/java/dagger/internal/codegen/ComponentValidator.java b/compiler/src/main/java/dagger/internal/codegen/ComponentValidator.java index 199c4a4bdab..bee627f5f67 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ComponentValidator.java +++ b/compiler/src/main/java/dagger/internal/codegen/ComponentValidator.java @@ -37,7 +37,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; -import com.google.common.collect.Multimap; +import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; import dagger.Component; import dagger.Reusable; @@ -157,7 +157,7 @@ public ComponentValidationReport validate(final TypeElement subject, // TODO(gak): This should use Util.findLocalAndInheritedMethods, otherwise // it can return a logical method multiple times (including overrides, etc.) List members = elements.getAllMembers(subject); - Multimap referencedSubcomponents = LinkedHashMultimap.create(); + SetMultimap referencedSubcomponents = LinkedHashMultimap.create(); for (ExecutableElement method : ElementFilter.methodsIn(members)) { if (method.getModifiers().contains(ABSTRACT)) { ExecutableType resolvedMethod = diff --git a/compiler/src/main/java/dagger/internal/codegen/ConfigurationAnnotations.java b/compiler/src/main/java/dagger/internal/codegen/ConfigurationAnnotations.java index d1091550b67..5c91e2aceee 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ConfigurationAnnotations.java +++ b/compiler/src/main/java/dagger/internal/codegen/ConfigurationAnnotations.java @@ -18,7 +18,10 @@ import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; import static com.google.auto.common.MoreElements.getAnnotationMirror; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static dagger.internal.codegen.Util.isAnyAnnotationPresent; +import static javax.lang.model.util.ElementFilter.typesIn; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; @@ -31,7 +34,10 @@ import com.google.common.collect.Sets; import dagger.Component; import dagger.Module; +import dagger.Subcomponent; import dagger.producers.ProducerModule; +import dagger.producers.ProductionComponent; +import dagger.producers.ProductionSubcomponent; import java.lang.annotation.Annotation; import java.util.ArrayDeque; import java.util.List; @@ -45,7 +51,6 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleAnnotationValueVisitor6; import javax.lang.model.util.SimpleTypeVisitor6; @@ -59,6 +64,35 @@ */ final class ConfigurationAnnotations { + static Optional getComponentAnnotation(TypeElement component) { + return getAnnotationMirror(component, Component.class) + .or(getAnnotationMirror(component, ProductionComponent.class)); + } + + static Optional getSubcomponentAnnotation(TypeElement subcomponent) { + return getAnnotationMirror(subcomponent, Subcomponent.class) + .or(getAnnotationMirror(subcomponent, ProductionSubcomponent.class)); + } + + static boolean isSubcomponent(Element element) { + return isAnyAnnotationPresent(element, Subcomponent.class, ProductionSubcomponent.class); + } + + static Optional getSubcomponentBuilder(TypeElement subcomponent) { + checkArgument(isSubcomponent(subcomponent)); + for (TypeElement nestedType : typesIn(subcomponent.getEnclosedElements())) { + if (isSubcomponentBuilder(nestedType)) { + return Optional.of(nestedType); + } + } + return Optional.absent(); + } + + static boolean isSubcomponentBuilder(Element element) { + return isAnyAnnotationPresent( + element, Subcomponent.Builder.class, ProductionSubcomponent.Builder.class); + } + private static final String MODULES_ATTRIBUTE = "modules"; static ImmutableList getComponentModules(AnnotationMirror componentAnnotation) { @@ -73,6 +107,11 @@ static ImmutableList getComponentDependencies(AnnotationMirror compo return convertClassArrayToListOfTypes(componentAnnotation, DEPENDENCIES_ATTRIBUTE); } + static Optional getModuleAnnotation(TypeElement moduleElement) { + return getAnnotationMirror(moduleElement, Module.class) + .or(getAnnotationMirror(moduleElement, ProducerModule.class)); + } + private static final String INCLUDES_ATTRIBUTE = "includes"; static ImmutableList getModuleIncludes(AnnotationMirror moduleAnnotation) { @@ -80,6 +119,13 @@ static ImmutableList getModuleIncludes(AnnotationMirror moduleAnnota return convertClassArrayToListOfTypes(moduleAnnotation, INCLUDES_ATTRIBUTE); } + private static final String SUBCOMPONENTS_ATTRIBUTE = "subcomponents"; + + static ImmutableList getModuleSubcomponents(AnnotationMirror moduleAnnotation) { + checkNotNull(moduleAnnotation); + return convertClassArrayToListOfTypes(moduleAnnotation, SUBCOMPONENTS_ATTRIBUTE); + } + private static final String INJECTS_ATTRIBUTE = "injects"; static ImmutableList getModuleInjects(AnnotationMirror moduleAnnotation) { @@ -156,7 +202,7 @@ protected ImmutableList defaultAction(Object o, String elementName) throw new IllegalArgumentException(elementName + " is not an array: " + o); } }; - + /** * Returns the value named {@code elementName} from {@code annotation}, which must be a member * that contains a single type. @@ -195,8 +241,7 @@ static ImmutableSet getTransitiveModules( for (TypeElement moduleElement = moduleQueue.poll(); moduleElement != null; moduleElement = moduleQueue.poll()) { - Optional moduleMirror = getAnnotationMirror(moduleElement, Module.class) - .or(getAnnotationMirror(moduleElement, ProducerModule.class)); + Optional moduleMirror = getModuleAnnotation(moduleElement); if (moduleMirror.isPresent()) { ImmutableSet.Builder moduleDependenciesBuilder = ImmutableSet.builder(); moduleDependenciesBuilder.addAll( @@ -221,7 +266,7 @@ static ImmutableSet getTransitiveModules( static ImmutableList enclosedBuilders(TypeElement typeElement, final Class annotation) { final ImmutableList.Builder builders = ImmutableList.builder(); - for (TypeElement element : ElementFilter.typesIn(typeElement.getEnclosedElements())) { + for (TypeElement element : typesIn(typeElement.getEnclosedElements())) { if (MoreElements.isAnnotationPresent(element, annotation)) { builders.add(MoreTypes.asDeclared(element.asType())); } @@ -237,8 +282,7 @@ private static void addIncludesFromSuperclasses(Types types, TypeElement element while (!types.isSameType(objectType, superclass) && superclass.getKind().equals(TypeKind.DECLARED)) { element = MoreElements.asType(types.asElement(superclass)); - Optional moduleMirror = getAnnotationMirror(element, Module.class) - .or(getAnnotationMirror(element, ProducerModule.class)); + Optional moduleMirror = getModuleAnnotation(element); if (moduleMirror.isPresent()) { builder.addAll(MoreTypes.asTypeElements(getModuleIncludes(moduleMirror.get()))); } diff --git a/compiler/src/main/java/dagger/internal/codegen/ContributionBinding.java b/compiler/src/main/java/dagger/internal/codegen/ContributionBinding.java index 56bc049d763..88436bbc507 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ContributionBinding.java +++ b/compiler/src/main/java/dagger/internal/codegen/ContributionBinding.java @@ -109,6 +109,13 @@ enum Kind { */ SYNTHETIC_DELEGATE_BINDING, + /** + * A synthetic binding for {@code Optional} of a type or a {@link javax.inject.Provider}, {@link + * dagger.Lazy}, or {@code Provider} of {@code Lazy} of a type. Generated by a {@link + * dagger.BindsOptionalOf} declaration. + */ + SYNTHETIC_OPTIONAL_BINDING, + // Provision kinds /** An {@link Inject}-annotated constructor. */ diff --git a/compiler/src/main/java/dagger/internal/codegen/DependencyRequest.java b/compiler/src/main/java/dagger/internal/codegen/DependencyRequest.java index 9a50c3cccef..f4d1d9f91b3 100644 --- a/compiler/src/main/java/dagger/internal/codegen/DependencyRequest.java +++ b/compiler/src/main/java/dagger/internal/codegen/DependencyRequest.java @@ -23,12 +23,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.internal.codegen.ConfigurationAnnotations.getNullableType; import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; import com.google.common.base.Function; import com.google.common.base.Optional; -import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.ListenableFuture; @@ -36,7 +36,6 @@ import dagger.Lazy; import dagger.MembersInjector; import dagger.Provides; -import dagger.internal.codegen.DependencyRequest.Factory.KindAndType; import dagger.producers.Produced; import dagger.producers.Producer; import java.util.List; @@ -125,11 +124,9 @@ Optional from(TypeMirror type) { return Optional.absent(); } - /** - * Returns a {@link KindAndType} with this kind and {@code type} type. - */ + /** Returns a {@link KindAndType} with this kind and {@code type} type. */ KindAndType ofType(TypeMirror type) { - return new AutoValue_DependencyRequest_Factory_KindAndType(this, type); + return new AutoValue_DependencyRequest_KindAndType(this, type); } } @@ -165,19 +162,75 @@ BindingKey bindingKey() { */ abstract Optional overriddenVariableName(); - /** A predicate that passes for requests with elements. */ - static final Predicate HAS_REQUEST_ELEMENT = - new Predicate() { - @Override - public boolean apply(DependencyRequest request) { - return request.requestElement().isPresent(); - } - }; - private static DependencyRequest.Builder builder() { return new AutoValue_DependencyRequest.Builder().isNullable(false); } + /** + * Extracts the dependency request type and kind from the type of a dependency request element. + * For example, if a user requests {@code Provider}, this will return ({@link Kind#PROVIDER}, + * {@code Foo}). + * + * @throws TypeNotPresentException if {@code type}'s kind is {@link TypeKind#ERROR}, which may + * mean that the type will be generated in a later round of processing + */ + static KindAndType extractKindAndType(TypeMirror type) { + return type.accept( + new SimpleTypeVisitor7() { + @Override + public KindAndType visitError(ErrorType errorType, Void p) { + throw new TypeNotPresentException(errorType.toString(), null); + } + + @Override + public KindAndType visitExecutable(ExecutableType executableType, Void p) { + return executableType.getReturnType().accept(this, null); + } + + @Override + public KindAndType visitDeclared(DeclaredType declaredType, Void p) { + return KindAndType.from(declaredType).or(defaultAction(declaredType, p)); + } + + @Override + protected KindAndType defaultAction(TypeMirror otherType, Void p) { + return Kind.INSTANCE.ofType(otherType); + } + }, + null); + } + + @AutoValue + abstract static class KindAndType { + abstract Kind kind(); + + abstract TypeMirror type(); + + static Optional from(TypeMirror type) { + for (Kind kind : Kind.values()) { + Optional kindAndType = kind.from(type); + if (kindAndType.isPresent()) { + return kindAndType.get().maybeProviderOfLazy().or(kindAndType); + } + } + return Optional.absent(); + } + + /** + * If {@code kindAndType} represents a {@link Kind#PROVIDER} of a {@code Lazy} for some type + * {@code T}, then this method returns ({@link Kind#PROVIDER_OF_LAZY}, {@code T}). + */ + private Optional maybeProviderOfLazy() { + if (kind().equals(Kind.PROVIDER)) { + Optional providedKindAndType = from(type()); + if (providedKindAndType.isPresent() && providedKindAndType.get().kind().equals(Kind.LAZY)) { + return Optional.of(Kind.PROVIDER_OF_LAZY.ofType(providedKindAndType.get().type())); + } + } + return Optional.absent(); + } + } + @CanIgnoreReturnValue @AutoValue.Builder abstract static class Builder { @@ -380,6 +433,23 @@ DependencyRequest forProductionComponentMonitor() { .build(); } + /** + * Returns a synthetic request for the present value of an optional binding generated from a + * {@link dagger.BindsOptionalOf} declaration. + */ + DependencyRequest forSyntheticPresentOptionalBinding(Key requestKey, Kind kind) { + Optional key = keyFactory.unwrapOptional(requestKey); + checkArgument(key.isPresent(), "not a request for optional: %s", requestKey); + return builder() + .kind(kind) + .key(key.get()) + .isNullable( + allowsNull( + extractKindAndType(OptionalType.from(requestKey).valueType()).kind(), + Optional.absent())) + .build(); + } + private DependencyRequest newDependencyRequest( Element requestElement, TypeMirror type, @@ -389,83 +459,24 @@ private DependencyRequest newDependencyRequest( if (kindAndType.kind().equals(Kind.MEMBERS_INJECTOR)) { checkArgument(!qualifier.isPresent()); } - // Only instance types can be non-null -- all other requests are wrapped - // inside something (e.g, Provider, Lazy, etc..). - // TODO(sameb): should Produced/Producer always require non-nullable? - boolean allowsNull = !kindAndType.kind().equals(Kind.INSTANCE) - || ConfigurationAnnotations.getNullableType(requestElement).isPresent(); return DependencyRequest.builder() .kind(kindAndType.kind()) .key(keyFactory.forQualifiedType(qualifier, kindAndType.type())) .requestElement(requestElement) - .isNullable(allowsNull) + .isNullable(allowsNull(kindAndType.kind(), getNullableType(requestElement))) .overriddenVariableName(name) .build(); } - @AutoValue - abstract static class KindAndType { - abstract Kind kind(); - abstract TypeMirror type(); - - static Optional from(TypeMirror type) { - for (Kind kind : Kind.values()) { - Optional kindAndType = kind.from(type); - if (kindAndType.isPresent()) { - return kindAndType.get().maybeProviderOfLazy().or(kindAndType); - } - } - return Optional.absent(); - } - - /** - * If {@code kindAndType} represents a {@link Kind#PROVIDER} of a {@code Lazy} for some - * type {@code T}, then this method returns ({@link Kind#PROVIDER_OF_LAZY}, {@code T}). - */ - private Optional maybeProviderOfLazy() { - if (kind().equals(Kind.PROVIDER)) { - Optional providedKindAndType = from(type()); - if (providedKindAndType.isPresent() - && providedKindAndType.get().kind().equals(Kind.LAZY)) { - return Optional.of(Kind.PROVIDER_OF_LAZY.ofType(providedKindAndType.get().type())); - } - } - return Optional.absent(); - } - } - /** - * Extracts the dependency request type and kind from the type of a dependency request element. - * For example, if a user requests {@code Provider}, this will return - * ({@link Kind#PROVIDER}, {@code Foo}). - * - * @throws TypeNotPresentException if {@code type}'s kind is {@link TypeKind#ERROR}, which may - * mean that the type will be generated in a later round of processing + * Returns {@code true} if a given request element allows null values. {@link Kind#INSTANCE} + * requests must be annotated with {@code @Nullable} in order to allow null values. All other + * request kinds implicitly allow null values because they are are wrapped inside {@link + * Provider}, {@link Lazy}, etc. */ - static KindAndType extractKindAndType(TypeMirror type) { - return type.accept( - new SimpleTypeVisitor7() { - @Override - public KindAndType visitError(ErrorType errorType, Void p) { - throw new TypeNotPresentException(errorType.toString(), null); - } - - @Override - public KindAndType visitExecutable(ExecutableType executableType, Void p) { - return executableType.getReturnType().accept(this, null); - } - - @Override - public KindAndType visitDeclared(DeclaredType declaredType, Void p) { - return KindAndType.from(declaredType).or(defaultAction(declaredType, p)); - } - - @Override - protected KindAndType defaultAction(TypeMirror otherType, Void p) { - return new AutoValue_DependencyRequest_Factory_KindAndType(Kind.INSTANCE, otherType); - } - }, - null); + // TODO(sameb): should Produced/Producer always require non-nullable? + private boolean allowsNull(Kind kind, Optional nullableType) { + return kind.equals(Kind.INSTANCE) ? nullableType.isPresent() : true; } } } diff --git a/compiler/src/main/java/dagger/internal/codegen/DependencyRequestFormatter.java b/compiler/src/main/java/dagger/internal/codegen/DependencyRequestFormatter.java index 5d5c6a5444f..6c809dfd4dc 100644 --- a/compiler/src/main/java/dagger/internal/codegen/DependencyRequestFormatter.java +++ b/compiler/src/main/java/dagger/internal/codegen/DependencyRequestFormatter.java @@ -17,22 +17,31 @@ package dagger.internal.codegen; import static com.google.auto.common.MoreElements.asExecutable; +import static dagger.internal.codegen.ErrorMessages.DOUBLE_INDENT; import static dagger.internal.codegen.ErrorMessages.INDENT; import com.google.auto.common.MoreElements; +import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.ListenableFuture; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import dagger.Lazy; import dagger.Provides; import dagger.internal.codegen.BindingGraphValidator.DependencyPath; +import dagger.internal.codegen.BindingGraphValidator.ResolvedRequest; import dagger.producers.Produces; +import java.util.List; import javax.inject.Inject; +import javax.inject.Provider; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementKindVisitor7; import javax.lang.model.util.Elements; @@ -79,9 +88,18 @@ String toDependencyTrace(DependencyPath dependencyPath) { return Joiner.on('\n') .join( dependencyPath - .dependencyRequests() - .filter(DependencyRequest.HAS_REQUEST_ELEMENT) - .transform(this) + .resolvedRequests() + .transform( + new Function() { + @Override + public String apply(ResolvedRequest resolvedRequest) { + ImmutableSet optionalBindingDeclarations = + resolvedRequest.dependentOptionalBindingDeclarations(); + return optionalBindingDeclarations.isEmpty() + ? format(resolvedRequest.dependencyRequest()) + : formatSyntheticOptionalBindingDependency(optionalBindingDeclarations); + } + }) .filter(Predicates.not(Predicates.equalTo(""))) .toList() .reverse()); @@ -91,6 +109,9 @@ String toDependencyTrace(DependencyPath dependencyPath) { // TODO(cgruber): consider returning a small structure containing strings to be indented later. @Override public String format(DependencyRequest request) { + if (!request.requestElement().isPresent()) { + return ""; + } return request .requestElement() .get() @@ -101,12 +122,14 @@ public String format(DependencyRequest request) { @Override public String visitExecutableAsMethod( ExecutableElement method, DependencyRequest request) { - StringBuilder builder = new StringBuilder(INDENT); - appendRequestedKeyAndVerb( - builder, - request.key().qualifier(), - request.key().type(), - componentMethodRequestVerb(request)); + StringBuilder builder = new StringBuilder(); + builder + .append(INDENT) + .append(formatKey(request.key())) + .append(" is ") + .append(componentMethodRequestVerb(request)) + .append(" at\n") + .append(DOUBLE_INDENT); appendEnclosingTypeAndMemberName(method, builder); builder.append('('); for (VariableElement parameter : method.getParameters()) { @@ -123,21 +146,18 @@ public String visitExecutableAsMethod( */ @Override public String visitVariableAsParameter( - final VariableElement variable, DependencyRequest request) { - StringBuilder builder = new StringBuilder(INDENT); - appendRequestedKeyAndVerb(request, builder); + VariableElement variable, DependencyRequest request) { + StringBuilder builder = new StringBuilder(); + appendRequestedTypeIsInjectedAt(builder, request); ExecutableElement methodOrConstructor = asExecutable(variable.getEnclosingElement()); appendEnclosingTypeAndMemberName(methodOrConstructor, builder).append('('); - int parameterIndex = methodOrConstructor.getParameters().indexOf(variable); - if (parameterIndex > 0) { - builder.append("…, "); - } - builder.append(variable.getSimpleName()); - if (parameterIndex < methodOrConstructor.getParameters().size() - 1) { - builder.append(", …"); - } + List parameters = methodOrConstructor.getParameters(); + int parameterIndex = parameters.indexOf(variable); + builder.append( + formatArgumentInList( + parameterIndex, parameters.size(), variable.getSimpleName())); builder.append(')'); return builder.toString(); } @@ -146,8 +166,8 @@ public String visitVariableAsParameter( @Override public String visitVariableAsField( VariableElement variable, DependencyRequest request) { - StringBuilder builder = new StringBuilder(INDENT); - appendRequestedKeyAndVerb(request, builder); + StringBuilder builder = new StringBuilder(); + appendRequestedTypeIsInjectedAt(builder, request); appendEnclosingTypeAndMemberName(variable, builder); return builder.toString(); } @@ -166,36 +186,50 @@ protected String defaultAction(Element element, DependencyRequest request) { request); } - private void appendRequestedKeyAndVerb(DependencyRequest request, StringBuilder builder) { - appendRequestedKeyAndVerb( - builder, request.key().qualifier(), requestedTypeWithFrameworkClass(request), "injected"); + @CanIgnoreReturnValue + private StringBuilder appendRequestedTypeIsInjectedAt( + StringBuilder builder, DependencyRequest request) { + return builder + .append(INDENT) + .append(formatKey(request.key().qualifier(), requestedType(request))) + .append(" is injected at\n") + .append(DOUBLE_INDENT); } - private void appendRequestedKeyAndVerb( - StringBuilder builder, - Optional qualifier, - TypeMirror requestedType, - String verb) { - appendQualifiedType(builder, qualifier, requestedType); - builder.append(" is ").append(verb).append(" at\n ").append(INDENT); - } + private TypeMirror requestedType(DependencyRequest request) { + TypeMirror keyType = request.key().type(); + switch (request.kind()) { + case FUTURE: + return wrapType(ListenableFuture.class, keyType); - private TypeMirror requestedTypeWithFrameworkClass(DependencyRequest request) { - Optional> requestFrameworkClass = request.kind().frameworkClass; - if (requestFrameworkClass.isPresent()) { - return types.getDeclaredType( - elements.getTypeElement(requestFrameworkClass.get().getCanonicalName()), - request.key().type()); + case PROVIDER_OF_LAZY: + return wrapType(Provider.class, wrapType(Lazy.class, keyType)); + + default: + if (request.kind().frameworkClass.isPresent()) { + return wrapType(request.kind().frameworkClass.get(), keyType); + } else { + return keyType; + } } - return request.key().type(); } - private void appendQualifiedType( - StringBuilder builder, Optional qualifier, TypeMirror type) { + private DeclaredType wrapType(Class wrapperType, TypeMirror wrappedType) { + return types.getDeclaredType( + elements.getTypeElement(wrapperType.getCanonicalName()), wrappedType); + } + + private String formatKey(Key key) { + return formatKey(key.qualifier(), key.type()); + } + + private String formatKey(Optional qualifier, TypeMirror type) { + StringBuilder builder = new StringBuilder(); if (qualifier.isPresent()) { builder.append(qualifier.get()).append(' '); } builder.append(type); + return builder.toString(); } /** @@ -231,4 +265,30 @@ private StringBuilder appendEnclosingTypeAndMemberName(Element member, StringBui .append('.') .append(member.getSimpleName()); } + + /** + * Returns a string of the form "{@code @BindsOptionalOf SomeKey is declared at Module.method()}", + * where {@code Module.method()} is the declaration. If there is more than one such declaration, + * one is chosen arbitrarily, and ", among others" is appended. + */ + private String formatSyntheticOptionalBindingDependency( + ImmutableSet optionalBindingDeclarations) { + OptionalBindingDeclaration optionalBindingDeclaration = + optionalBindingDeclarations.iterator().next(); + StringBuilder builder = new StringBuilder(); + builder + .append(INDENT) + .append("@BindsOptionalOf ") + .append(formatKey(optionalBindingDeclaration.key())) + .append(" is declared at\n") + .append(DOUBLE_INDENT); + + appendEnclosingTypeAndMemberName(optionalBindingDeclaration.bindingElement().get(), builder); + builder.append("()"); + if (optionalBindingDeclarations.size() > 1) { + builder.append(", among others"); + } + + return builder.toString(); + } } diff --git a/compiler/src/main/java/dagger/internal/codegen/ErrorMessages.java b/compiler/src/main/java/dagger/internal/codegen/ErrorMessages.java index 287409bee78..bf709f29294 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ErrorMessages.java +++ b/compiler/src/main/java/dagger/internal/codegen/ErrorMessages.java @@ -16,6 +16,10 @@ package dagger.internal.codegen; +import static dagger.internal.codegen.ConfigurationAnnotations.getSubcomponentAnnotation; +import static dagger.internal.codegen.MoreAnnotationMirrors.simpleName; + +import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import dagger.Multibindings; import dagger.Provides; @@ -23,6 +27,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; /** @@ -36,6 +41,7 @@ final class ErrorMessages { * Common constants. */ static final String INDENT = " "; + static final String DOUBLE_INDENT = INDENT + INDENT; static final int DUPLICATE_SIZE_LIMIT = 10; /* @@ -181,8 +187,15 @@ static String inconsistentMapKeyAnnotationsError(String key) { static final String BINDS_METHOD_ONE_ASSIGNABLE_PARAMETER = "@Binds methods must have only one parameter whose type is assignable to the return type"; - static final String BINDING_METHOD_NOT_IN_MODULE = - "@%s methods can only be present within a @%s"; + static final String BINDS_OPTIONAL_OF_METHOD_HAS_PARAMETER = + "@BindsOptionalOf methods must not have parameters"; + + static final String BINDS_OPTIONAL_OF_METHOD_RETURNS_IMPLICITLY_PROVIDED_TYPE = + "@BindsOptionalOf methods cannot " + + "return unqualified types that have an @Inject-annotated constructor because those are " + + "always present"; + + static final String BINDING_METHOD_NOT_IN_MODULE = "@%s methods can only be present within a @%s"; static final String BINDS_ELEMENTS_INTO_SET_METHOD_RETURN_SET = "@Binds @ElementsIntoSet methods must return a Set and take a Set parameter"; @@ -491,13 +504,38 @@ static final class MultibindsMessages { static final String METHOD_MUST_RETURN_MAP_OR_SET = "@%s methods must return Map or Set"; - static final String NO_MAP_KEY = "@%s methods must not have a @MapKey annotation"; - static final String PARAMETERS = "@%s methods cannot have parameters"; private MultibindsMessages() {} } + static class ModuleMessages { + static String moduleSubcomponentsIncludesBuilder(TypeElement moduleSubcomponentsAttribute) { + TypeElement subcomponentType = + MoreElements.asType(moduleSubcomponentsAttribute.getEnclosingElement()); + return String.format( + "%s is a @%s.Builder. Did you mean to use %s?", + moduleSubcomponentsAttribute.getQualifiedName(), + simpleName(getSubcomponentAnnotation(subcomponentType).get()), + subcomponentType.getQualifiedName()); + } + + static String moduleSubcomponentsIncludesNonSubcomponent( + TypeElement moduleSubcomponentsAttribute) { + return moduleSubcomponentsAttribute.getQualifiedName() + + " is not a @Subcomponent or @ProductionSubcomponent"; + } + + static String moduleSubcomponentsDoesntHaveBuilder( + TypeElement subcomponent, AnnotationMirror moduleAnnotation) { + return String.format( + "%s doesn't have a @%s.Builder, which is required when used with @%s.subcomponents", + subcomponent.getQualifiedName(), + simpleName(getSubcomponentAnnotation(subcomponent).get()), + simpleName(moduleAnnotation)); + } + } + /** * A regular expression to match a small list of specific packages deemed to * be unhelpful to display in fully qualified types in error messages. diff --git a/compiler/src/main/java/dagger/internal/codegen/Formatter.java b/compiler/src/main/java/dagger/internal/codegen/Formatter.java index 714b63f5ff3..da3cb4789b6 100644 --- a/compiler/src/main/java/dagger/internal/codegen/Formatter.java +++ b/compiler/src/main/java/dagger/internal/codegen/Formatter.java @@ -16,6 +16,7 @@ package dagger.internal.codegen; +import static com.google.common.base.Preconditions.checkElementIndex; import static dagger.internal.codegen.ErrorMessages.INDENT; import com.google.common.base.Function; @@ -85,4 +86,17 @@ private void appendIndent(StringBuilder builder, int indentLevel) { builder.append(INDENT); } } + + protected String formatArgumentInList(int index, int size, CharSequence name) { + checkElementIndex(index, size); + StringBuilder builder = new StringBuilder(); + if (index > 0) { + builder.append("…, "); + } + builder.append(name); + if (index < size - 1) { + builder.append(", …"); + } + return builder.toString(); + } } diff --git a/compiler/src/main/java/dagger/internal/codegen/FrameworkType.java b/compiler/src/main/java/dagger/internal/codegen/FrameworkType.java new file mode 100644 index 00000000000..15cb4fccf74 --- /dev/null +++ b/compiler/src/main/java/dagger/internal/codegen/FrameworkType.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2016 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.base.CaseFormat.UPPER_CAMEL; +import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; +import static dagger.internal.codegen.DependencyRequest.Kind.INSTANCE; + +import com.google.common.util.concurrent.Futures; +import com.squareup.javapoet.CodeBlock; +import dagger.MembersInjector; +import dagger.internal.DoubleCheck; +import dagger.internal.ProviderOfLazy; +import dagger.producers.Produced; +import dagger.producers.Producer; +import dagger.producers.internal.Producers; +import javax.inject.Provider; + +/** One of the core types initialized as fields in a generated component. */ +enum FrameworkType { + /** A {@link Provider}. */ + PROVIDER { + @Override + CodeBlock to(DependencyRequest.Kind requestKind, CodeBlock from) { + switch (requestKind) { + case INSTANCE: + return CodeBlock.of("$L.get()", from); + + case LAZY: + return CodeBlock.of("$T.lazy($L)", DoubleCheck.class, from); + + case PROVIDER: + return from; + + case PROVIDER_OF_LAZY: + return CodeBlock.of("$T.create($L)", ProviderOfLazy.class, from); + + case PRODUCER: + return CodeBlock.of("$T.producerFromProvider($L)", Producers.class, from); + + case FUTURE: + return CodeBlock.of("$T.immediateFuture($L)", Futures.class, to(INSTANCE, from)); + + case PRODUCED: + return CodeBlock.of("$T.successful($L)", Produced.class, to(INSTANCE, from)); + + default: + throw new IllegalArgumentException( + String.format("Cannot request a %s from a %s", requestKind, this)); + } + } + }, + + /** A {@link Producer}. */ + PRODUCER { + @Override + CodeBlock to(DependencyRequest.Kind requestKind, CodeBlock from) { + switch (requestKind) { + case FUTURE: + return CodeBlock.of("$L.get()", from); + + case PRODUCER: + return from; + + default: + throw new IllegalArgumentException( + String.format("Cannot request a %s from a %s", requestKind, this)); + } + } + }, + + /** A {@link MembersInjector}. */ + MEMBERS_INJECTOR { + @Override + CodeBlock to(DependencyRequest.Kind requestKind, CodeBlock from) { + switch (requestKind) { + case MEMBERS_INJECTOR: + return from; + + default: + throw new IllegalArgumentException( + String.format("Cannot request a %s from a %s", requestKind, this)); + } + } + }, + ; + + /** + * Returns an expression that evaluates to a requested object given an expression that evaluates + * to an instance of this framework type. + * + * @param requestKind the kind of {@link DependencyRequest} that the returned expression can + * satisfy + * @param from an expression that evaluates to an instance of this framework type + * @throws IllegalArgumentException if a valid expression cannot be generated for {@code + * requestKind} + */ + abstract CodeBlock to(DependencyRequest.Kind requestKind, CodeBlock from); + + @Override + public String toString() { + return UPPER_UNDERSCORE.to(UPPER_CAMEL, super.toString()); + } +} diff --git a/compiler/src/main/java/dagger/internal/codegen/InjectBindingRegistry.java b/compiler/src/main/java/dagger/internal/codegen/InjectBindingRegistry.java index b1b5bac4ce2..be4bcf9221a 100644 --- a/compiler/src/main/java/dagger/internal/codegen/InjectBindingRegistry.java +++ b/compiler/src/main/java/dagger/internal/codegen/InjectBindingRegistry.java @@ -16,19 +16,16 @@ package dagger.internal.codegen; -import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static dagger.internal.codegen.InjectionAnnotations.injectedConstructors; import static dagger.internal.codegen.MembersInjectionBinding.Strategy.INJECT_MEMBERS; import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding; -import static javax.lang.model.util.ElementFilter.constructorsIn; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; @@ -305,18 +302,6 @@ Optional getOrFindProvisionBinding(Key key) { } } - private ImmutableSet injectedConstructors(TypeElement element) { - return FluentIterable.from(constructorsIn(element.getEnclosedElements())) - .filter( - new Predicate() { - @Override - public boolean apply(ExecutableElement constructor) { - return isAnnotationPresent(constructor, Inject.class); - } - }) - .toSet(); - } - /** * Returns a {@link MembersInjectionBinding} for {@code key}. If none has been registered yet, * registers one, along with all necessary members injection bindings for superclasses. diff --git a/compiler/src/main/java/dagger/internal/codegen/InjectionAnnotations.java b/compiler/src/main/java/dagger/internal/codegen/InjectionAnnotations.java index bfc9773dc65..403f2e7d5ef 100644 --- a/compiler/src/main/java/dagger/internal/codegen/InjectionAnnotations.java +++ b/compiler/src/main/java/dagger/internal/codegen/InjectionAnnotations.java @@ -16,15 +16,22 @@ package dagger.internal.codegen; +import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.Preconditions.checkNotNull; +import static javax.lang.model.util.ElementFilter.constructorsIn; import com.google.auto.common.AnnotationMirrors; import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableSet; +import javax.inject.Inject; import javax.inject.Qualifier; import javax.inject.Scope; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; /** * Utilities relating to annotations defined in the {@code javax.inject} package. @@ -55,5 +62,18 @@ static ImmutableSet getScopes(Element element) { return AnnotationMirrors.getAnnotatedAnnotations(element, Scope.class); } + /** Returns the constructors in {@code type} that are annotated with {@link Inject}. */ + static ImmutableSet injectedConstructors(TypeElement type) { + return FluentIterable.from(constructorsIn(type.getEnclosedElements())) + .filter( + new Predicate() { + @Override + public boolean apply(ExecutableElement constructor) { + return isAnnotationPresent(constructor, Inject.class); + } + }) + .toSet(); + } + private InjectionAnnotations() {} } diff --git a/compiler/src/main/java/dagger/internal/codegen/Key.java b/compiler/src/main/java/dagger/internal/codegen/Key.java index fd3de007cf9..dd37e9c075e 100644 --- a/compiler/src/main/java/dagger/internal/codegen/Key.java +++ b/compiler/src/main/java/dagger/internal/codegen/Key.java @@ -43,6 +43,7 @@ import com.google.common.collect.Multimaps; import com.google.common.util.concurrent.ListenableFuture; import dagger.Binds; +import dagger.BindsOptionalOf; import dagger.Multibindings; import dagger.multibindings.Multibinds; import dagger.producers.Produced; @@ -54,6 +55,7 @@ import java.util.Set; import java.util.concurrent.Executor; import javax.inject.Provider; +import javax.inject.Qualifier; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -208,45 +210,54 @@ boolean isValidMembersInjectionKey() { } /** - * Returns true if the key is valid as an implicit key (that is, if it's valid for a just-in-time - * binding by discovering an {@code @Inject} constructor). + * Returns {@code true} if this is valid as an implicit key (that is, if it's valid for a + * just-in-time binding by discovering an {@code @Inject} constructor). */ - boolean isValidImplicitProvisionKey(final Types types) { + boolean isValidImplicitProvisionKey(Types types) { + return isValidImplicitProvisionKey(qualifier(), type(), types); + } + + /** + * Returns {@code true} if a key with {@code qualifier} and {@code type} is valid as an implicit + * key (that is, if it's valid for a just-in-time binding by discovering an {@code @Inject} + * constructor). + */ + static boolean isValidImplicitProvisionKey( + Optional qualifier, TypeMirror type, final Types types) { // Qualifiers disqualify implicit provisioning. - if (qualifier().isPresent()) { + if (qualifier.isPresent()) { return false; } - return type().accept(new SimpleTypeVisitor6() { - @Override protected Boolean defaultAction(TypeMirror e, Void p) { - return false; // Only declared types are allowed. - } - - @Override public Boolean visitDeclared(DeclaredType type, Void ignored) { - // Non-classes or abstract classes aren't allowed. - TypeElement element = MoreElements.asType(type.asElement()); - if (!element.getKind().equals(ElementKind.CLASS) - || element.getModifiers().contains(Modifier.ABSTRACT)) { - return false; - } - - // If the key has type arguments, validate that each type argument is declared. - // Otherwise the type argument may be a wildcard (or other type), and we can't - // resolve that to actual types. - for (TypeMirror arg : type.getTypeArguments()) { - if (arg.getKind() != TypeKind.DECLARED) { - return false; + return type.accept( + new SimpleTypeVisitor6(false) { + @Override + public Boolean visitDeclared(DeclaredType type, Void ignored) { + // Non-classes or abstract classes aren't allowed. + TypeElement element = MoreElements.asType(type.asElement()); + if (!element.getKind().equals(ElementKind.CLASS) + || element.getModifiers().contains(Modifier.ABSTRACT)) { + return false; + } + + // If the key has type arguments, validate that each type argument is declared. + // Otherwise the type argument may be a wildcard (or other type), and we can't + // resolve that to actual types. + for (TypeMirror arg : type.getTypeArguments()) { + if (arg.getKind() != TypeKind.DECLARED) { + return false; + } + } + + // Also validate that the key is not the erasure of a generic type. + // If it is, that means the user referred to Foo as just 'Foo', + // which we don't allow. (This is a judgement call -- we *could* + // allow it and instantiate the type bounds... but we don't.) + return MoreTypes.asDeclared(element.asType()).getTypeArguments().isEmpty() + || !types.isSameType(types.erasure(element.asType()), type); } - } - - // Also validate that the key is not the erasure of a generic type. - // If it is, that means the user referred to Foo as just 'Foo', - // which we don't allow. (This is a judgement call -- we *could* - // allow it and instantiate the type bounds... but we don't.) - return MoreTypes.asDeclared(element.asType()).getTypeArguments().isEmpty() - || !types.isSameType(types.erasure(element.asType()), type()); - } - }, null); + }, + null); } /** @@ -336,28 +347,51 @@ Key forSubcomponentBuilderMethod( return forMethod(subcomponentBuilderMethod, returnType); } + Key forSubcomponentBuilder(TypeMirror builderType) { + checkNotNull(builderType); + return new AutoValue_Key( + Optional.>absent(), + MoreTypes.equivalence().wrap(builderType), + Optional.absent()); + } + Key forProvidesMethod(ExecutableElement method, TypeElement contributingModule) { - return forProvidesOrProducesMethod(method, contributingModule, getProviderElement()); + return forBindingMethod(method, contributingModule, Optional.of(getProviderElement())); } Key forProducesMethod(ExecutableElement method, TypeElement contributingModule) { - return forProvidesOrProducesMethod(method, contributingModule, getProducerElement()); + return forBindingMethod(method, contributingModule, Optional.of(getProducerElement())); + } + + /** Returns the key bound by a {@link Binds} method. */ + Key forBindsMethod(ExecutableElement method, TypeElement contributingModule) { + checkArgument(isAnnotationPresent(method, Binds.class)); + return forBindingMethod(method, contributingModule, Optional.absent()); } - private Key forProvidesOrProducesMethod( - ExecutableElement method, TypeElement contributingModule, TypeElement frameworkType) { + /** Returns the base key bound by a {@link BindsOptionalOf} method. */ + Key forBindsOptionalOfMethod(ExecutableElement method, TypeElement contributingModule) { + checkArgument(isAnnotationPresent(method, BindsOptionalOf.class)); + return forBindingMethod(method, contributingModule, Optional.absent()); + } + + private Key forBindingMethod( + ExecutableElement method, + TypeElement contributingModule, + Optional frameworkType) { checkArgument(method.getKind().equals(METHOD)); ExecutableType methodType = MoreTypes.asExecutable( types.asMemberOf(MoreTypes.asDeclared(contributingModule.asType()), method)); ContributionType contributionType = ContributionType.fromBindingMethod(method); TypeMirror returnType = normalize(types, methodType.getReturnType()); - if (frameworkType.equals(getProducerElement()) + if (frameworkType.isPresent() + && frameworkType.get().equals(getProducerElement()) && MoreTypes.isTypeOf(ListenableFuture.class, returnType)) { returnType = Iterables.getOnlyElement(MoreTypes.asDeclared(returnType).getTypeArguments()); } TypeMirror keyType = - bindingMethodKeyType(returnType, method, contributionType, Optional.of(frameworkType)); + bindingMethodKeyType(returnType, method, contributionType, frameworkType); Key key = forMethod(method, keyType); return contributionType.equals(ContributionType.UNIQUE) ? key @@ -388,26 +422,6 @@ Key forMultibindsMethod( return forMethod(method, keyType); } - /** Returns the key bound by a {@link Binds} method. */ - Key forBindsMethod(ExecutableElement method, TypeElement contributingModule) { - checkArgument(isAnnotationPresent(method, Binds.class)); - ContributionType contributionType = ContributionType.fromBindingMethod(method); - TypeMirror returnType = - normalize( - types, - MoreTypes.asExecutable( - types.asMemberOf(MoreTypes.asDeclared(contributingModule.asType()), method)) - .getReturnType()); - TypeMirror keyType = - bindingMethodKeyType( - returnType, method, contributionType, Optional.absent()); - Key key = forMethod(method, keyType); - return contributionType.equals(ContributionType.UNIQUE) - ? key - : key.withMultibindingContributionIdentifier( - new MultibindingContributionIdentifier(method, contributingModule)); - } - private TypeMirror bindingMethodKeyType( TypeMirror returnType, ExecutableElement method, @@ -653,5 +667,19 @@ Optional unwrapSetKey(Key key, Class wrappingClass) { } return Optional.absent(); } + + /** + * If {@code key}'s type is {@code Optional} for some {@code T}, returns a key with the same + * qualifier whose type is {@linkplain DependencyRequest#extractKindAndType(TypeMirror) + * extracted} from {@code T}. + */ + Optional unwrapOptional(Key key) { + if (!OptionalType.isOptional(key)) { + return Optional.absent(); + } + TypeMirror underlyingType = + DependencyRequest.extractKindAndType(OptionalType.from(key).valueType()).type(); + return Optional.of(key.withType(types, underlyingType)); + } } } diff --git a/compiler/src/main/java/dagger/internal/codegen/MapKeyProcessingStep.java b/compiler/src/main/java/dagger/internal/codegen/MapKeyProcessingStep.java index 2dfe472c1cb..241e61771ba 100644 --- a/compiler/src/main/java/dagger/internal/codegen/MapKeyProcessingStep.java +++ b/compiler/src/main/java/dagger/internal/codegen/MapKeyProcessingStep.java @@ -16,8 +16,6 @@ package dagger.internal.codegen; -import static dagger.internal.codegen.MapKeyGenerator.MapKeyCreatorSpecification.unwrappedMapKeyWithAnnotationValue; -import static dagger.internal.codegen.MapKeyGenerator.MapKeyCreatorSpecification.wrappedMapKey; import static dagger.internal.codegen.MapKeys.getUnwrappedMapKeyType; import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE; import static javax.lang.model.util.ElementFilter.typesIn; @@ -31,6 +29,7 @@ import java.util.Set; import javax.annotation.processing.Messager; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.util.Types; @@ -46,17 +45,20 @@ public class MapKeyProcessingStep implements BasicAnnotationProcessor.Processing private final Messager messager; private final Types types; private final MapKeyValidator mapKeyValidator; - private final MapKeyGenerator mapKeyGenerator; + private final AnnotationCreatorGenerator annotationCreatorGenerator; + private final UnwrappedMapKeyGenerator unwrappedMapKeyGenerator; MapKeyProcessingStep( Messager messager, Types types, MapKeyValidator mapKeyValidator, - MapKeyGenerator mapKeyGenerator) { + AnnotationCreatorGenerator annotationCreatorGenerator, + UnwrappedMapKeyGenerator unwrappedMapKeyGenerator) { this.messager = messager; this.types = types; this.mapKeyValidator = mapKeyValidator; - this.mapKeyGenerator = mapKeyGenerator; + this.annotationCreatorGenerator = annotationCreatorGenerator; + this.unwrappedMapKeyGenerator = unwrappedMapKeyGenerator; } @Override @@ -67,26 +69,25 @@ public Set> annotations() { @Override public Set process( SetMultimap, Element> elementsByAnnotation) { - for (TypeElement mapKeyAnnotation : typesIn(elementsByAnnotation.get(MapKey.class))) { - ValidationReport mapKeyReport = mapKeyValidator.validate(mapKeyAnnotation); + for (TypeElement mapKeyAnnotationType : typesIn(elementsByAnnotation.get(MapKey.class))) { + ValidationReport mapKeyReport = mapKeyValidator.validate(mapKeyAnnotationType); mapKeyReport.printMessagesTo(messager); if (mapKeyReport.isClean()) { - MapKey mapkey = mapKeyAnnotation.getAnnotation(MapKey.class); - if (mapkey.unwrapValue()) { - DeclaredType keyType = - getUnwrappedMapKeyType(MoreTypes.asDeclared(mapKeyAnnotation.asType()), types); - if (keyType.asElement().getKind().equals(ANNOTATION_TYPE)) { - mapKeyGenerator.generate( - unwrappedMapKeyWithAnnotationValue( - mapKeyAnnotation, MoreTypes.asTypeElement(keyType)), - messager); - } - } else { - mapKeyGenerator.generate(wrappedMapKey(mapKeyAnnotation), messager); + MapKey mapkey = mapKeyAnnotationType.getAnnotation(MapKey.class); + if (!mapkey.unwrapValue()) { + annotationCreatorGenerator.generate(mapKeyAnnotationType, messager); + } else if (unwrappedValueKind(mapKeyAnnotationType).equals(ANNOTATION_TYPE)) { + unwrappedMapKeyGenerator.generate(mapKeyAnnotationType, messager); } } } return ImmutableSet.of(); } + + private ElementKind unwrappedValueKind(TypeElement mapKeyAnnotationType) { + DeclaredType unwrappedMapKeyType = + getUnwrappedMapKeyType(MoreTypes.asDeclared(mapKeyAnnotationType.asType()), types); + return unwrappedMapKeyType.asElement().getKind(); + } } diff --git a/compiler/src/main/java/dagger/internal/codegen/MapKeys.java b/compiler/src/main/java/dagger/internal/codegen/MapKeys.java index c086d14209d..3aec5796b07 100644 --- a/compiler/src/main/java/dagger/internal/codegen/MapKeys.java +++ b/compiler/src/main/java/dagger/internal/codegen/MapKeys.java @@ -20,35 +20,23 @@ import static com.google.auto.common.AnnotationMirrors.getAnnotationValuesWithDefaults; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.getOnlyElement; -import static com.google.common.collect.Iterables.transform; -import static dagger.internal.codegen.CodeBlocks.makeParametersCodeBlock; -import static dagger.internal.codegen.SourceFiles.classFileName; import static javax.lang.model.util.ElementFilter.methodsIn; import com.google.auto.common.MoreTypes; -import com.google.common.base.Function; import com.google.common.base.Optional; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.TypeName; import dagger.MapKey; -import java.util.List; -import java.util.Map; import java.util.NoSuchElementException; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleAnnotationValueVisitor6; import javax.lang.model.util.SimpleTypeVisitor6; import javax.lang.model.util.Types; @@ -133,15 +121,6 @@ public DeclaredType visitDeclared(DeclaredType t, Void p) { return keyTypeElementVisitor.visit(onlyElement.getReturnType()); } - /** - * Returns the name of the generated class that contains the static {@code create} methods for a - * {@link MapKey} annotation type. - */ - public static ClassName getMapKeyCreatorClassName(TypeElement mapKeyType) { - ClassName mapKeyTypeName = ClassName.get(mapKeyType); - return mapKeyTypeName.topLevelClassName().peerClass(classFileName(mapKeyTypeName) + "Creator"); - } - /** * Returns a code block for the map key specified by the {@link MapKey} annotation on * {@code bindingElement}. @@ -152,183 +131,16 @@ public static ClassName getMapKeyCreatorClassName(TypeElement mapKeyType) { * annotation */ static CodeBlock getMapKeyExpression(AnnotationMirror mapKey) { - ClassName mapKeyCreator = - getMapKeyCreatorClassName(MoreTypes.asTypeElement(mapKey.getAnnotationType())); Optional unwrappedValue = unwrapValue(mapKey); + AnnotationExpression annotationExpression = new AnnotationExpression(mapKey); if (unwrappedValue.isPresent()) { - return new MapKeyExpressionExceptArrays(mapKeyCreator) - .visit(unwrappedValue.get(), unwrappedValue.get()); + TypeMirror unwrappedValueType = + getOnlyElement(getAnnotationValuesWithDefaults(mapKey).keySet()).getReturnType(); + return annotationExpression.getValueExpression(unwrappedValueType, unwrappedValue.get()); } else { - return annotationExpression(mapKey, new MapKeyExpression(mapKeyCreator)); + return annotationExpression.getAnnotationInstanceExpression(); } } - /** - * Returns a code block to create the visited value in code. Expects its parameter to be a class - * with static creation methods for all nested annotation types. - * - *

Note that {@link AnnotationValue#toString()} is the source-code representation of the value - * when used in an annotation, which is not always the same as the representation needed - * when creating the value in a method body. - * - *

For example, inside an annotation, a nested array of {@code int}s is simply - * {@code {1, 2, 3}}, but in code it would have to be {@code new int[] {1, 2, 3}}. - */ - private static class MapKeyExpression - extends SimpleAnnotationValueVisitor6 { - - final ClassName mapKeyCreator; - - MapKeyExpression(ClassName mapKeyCreator) { - this.mapKeyCreator = mapKeyCreator; - } - - @Override - public CodeBlock visitEnumConstant(VariableElement c, AnnotationValue p) { - return CodeBlock.of( - "$T.$L", TypeName.get(c.getEnclosingElement().asType()), c.getSimpleName()); - } - - @Override - public CodeBlock visitAnnotation(AnnotationMirror a, AnnotationValue p) { - return annotationExpression(a, this); - } - - @Override - public CodeBlock visitType(TypeMirror t, AnnotationValue p) { - return CodeBlock.of("$T.class", TypeName.get(t)); - } - - @Override - public CodeBlock visitString(String s, AnnotationValue p) { - return CodeBlock.of("$S", s); - } - - @Override - public CodeBlock visitByte(byte b, AnnotationValue p) { - return CodeBlock.of("(byte) $L", b); - } - - @Override - public CodeBlock visitChar(char c, AnnotationValue p) { - return CodeBlock.of("$L", p); - } - - @Override - public CodeBlock visitDouble(double d, AnnotationValue p) { - return CodeBlock.of("$LD", d); - } - - @Override - public CodeBlock visitFloat(float f, AnnotationValue p) { - return CodeBlock.of("$LF", f); - } - - @Override - public CodeBlock visitInt(int i, AnnotationValue p) { - return CodeBlock.of("(int) $L", i); - } - - @Override - public CodeBlock visitLong(long i, AnnotationValue p) { - return CodeBlock.of("$LL", i); - } - - @Override - public CodeBlock visitShort(short s, AnnotationValue p) { - return CodeBlock.of("(short) $L", s); - } - - @Override - protected CodeBlock defaultAction(Object o, AnnotationValue p) { - return CodeBlock.of("$L", o); - } - - @Override - public CodeBlock visitArray(List values, AnnotationValue p) { - ImmutableList.Builder codeBlocks = ImmutableList.builder(); - for (int i = 0; i < values.size(); i++) { - codeBlocks.add(this.visit(values.get(i), p)); - } - return CodeBlock.of("{$L}", makeParametersCodeBlock(codeBlocks.build())); - } - } - - /** - * Returns a code block for the visited value. Expects its parameter to be a class with static - * creation methods for all nested annotation types. - * - *

Throws {@link IllegalArgumentException} if the visited value is an array. - */ - private static class MapKeyExpressionExceptArrays extends MapKeyExpression { - - MapKeyExpressionExceptArrays(ClassName mapKeyCreator) { - super(mapKeyCreator); - } - - @Override - public CodeBlock visitArray(List values, AnnotationValue p) { - throw new IllegalArgumentException("Cannot unwrap arrays"); - } - } - - /** - * Returns a code block that calls a static method on {@code mapKeyCodeBlock.mapKeyCreator} to - * create an annotation from {@code mapKeyAnnotation}. - */ - private static CodeBlock annotationExpression( - AnnotationMirror mapKeyAnnotation, final MapKeyExpression mapKeyExpression) { - return CodeBlock.of( - "$T.create$L($L)", - mapKeyExpression.mapKeyCreator, - mapKeyAnnotation.getAnnotationType().asElement().getSimpleName(), - makeParametersCodeBlock( - transform( - getAnnotationValuesWithDefaults(mapKeyAnnotation).entrySet(), - new Function, CodeBlock>() { - @Override - public CodeBlock apply(Map.Entry entry) { - return ARRAY_LITERAL_PREFIX.visit( - entry.getKey().getReturnType(), - mapKeyExpression.visit(entry.getValue(), entry.getValue())); - } - }))); - } - - /** - * If the visited type is an array, prefixes the parameter code block with {@code new T[]}, where - * {@code T} is the raw array component type. - */ - private static final SimpleTypeVisitor6 ARRAY_LITERAL_PREFIX = - new SimpleTypeVisitor6() { - - @Override - public CodeBlock visitArray(ArrayType t, CodeBlock p) { - return CodeBlock.of("new $T[] $L", RAW_TYPE_NAME.visit(t.getComponentType()), p); - } - - @Override - protected CodeBlock defaultAction(TypeMirror e, CodeBlock p) { - return p; - } - }; - - /** - * If the visited type is an array, returns the name of its raw component type; otherwise returns - * the name of the type itself. - */ - private static final SimpleTypeVisitor6 RAW_TYPE_NAME = - new SimpleTypeVisitor6() { - @Override - public TypeName visitDeclared(DeclaredType t, Void p) { - return ClassName.get(MoreTypes.asTypeElement(t)); - } - - @Override - protected TypeName defaultAction(TypeMirror e, Void p) { - return TypeName.get(e); - } - }; - private MapKeys() {} } diff --git a/compiler/src/main/java/dagger/internal/codegen/MissingBindingSuggestions.java b/compiler/src/main/java/dagger/internal/codegen/MissingBindingSuggestions.java index 3841548099c..8f86422b00e 100644 --- a/compiler/src/main/java/dagger/internal/codegen/MissingBindingSuggestions.java +++ b/compiler/src/main/java/dagger/internal/codegen/MissingBindingSuggestions.java @@ -40,7 +40,7 @@ static ImmutableList forKey(BindingGraph topLevelGraph, BindingKey key) BindingGraph graph = graphsToTry.removeLast(); ResolvedBindings bindings = graph.resolvedBindings().get(key); if ((bindings == null) || bindings.bindings().isEmpty()) { - graphsToTry.addAll(graph.subgraphs().values()); + graphsToTry.addAll(graph.subgraphs()); } else { resolutions.add("A binding with matching key exists in component: " + graph.componentDescriptor().componentDefinitionType().getQualifiedName()); diff --git a/compiler/src/main/java/dagger/internal/codegen/ModuleDescriptor.java b/compiler/src/main/java/dagger/internal/codegen/ModuleDescriptor.java index bda1fb58466..d50fa054a52 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ModuleDescriptor.java +++ b/compiler/src/main/java/dagger/internal/codegen/ModuleDescriptor.java @@ -19,8 +19,8 @@ import static com.google.auto.common.MoreElements.getAnnotationMirror; import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; +import static dagger.internal.codegen.ConfigurationAnnotations.getModuleAnnotation; import static dagger.internal.codegen.ConfigurationAnnotations.getModuleIncludes; import static javax.lang.model.type.TypeKind.DECLARED; import static javax.lang.model.type.TypeKind.NONE; @@ -36,6 +36,7 @@ import com.google.common.collect.Iterables; import com.google.errorprone.annotations.CanIgnoreReturnValue; import dagger.Binds; +import dagger.BindsOptionalOf; import dagger.Module; import dagger.Multibindings; import dagger.Provides; @@ -68,16 +69,18 @@ static final Function getModuleElement() { abstract ImmutableSet bindings(); - /** - * The multibinding declarations contained in this module. - */ + /** The multibinding declarations contained in this module. */ abstract ImmutableSet multibindingDeclarations(); - /** - * The {@link Binds} method declarations that define delegate bindings. - */ + /** The {@link Module#subcomponents() subcomponent declarations} contained in this module. */ + abstract ImmutableSet subcomponentDeclarations(); + + /** The {@link Binds} method declarations that define delegate bindings. */ abstract ImmutableSet delegateDeclarations(); + /** The {@link BindsOptionalOf} method declarations that define optional bindings. */ + abstract ImmutableSet optionalDeclarations(); + enum Kind { MODULE( Module.class, Provides.class, ImmutableSet.of(Module.class)), @@ -118,6 +121,10 @@ static Optional forAnnotatedElement(TypeElement element) { this.includesTypes = includesTypes; } + Optional getModuleAnnotationMirror(TypeElement element) { + return getAnnotationMirror(element, moduleAnnotation); + } + Class moduleAnnotation() { return moduleAnnotation; } @@ -137,29 +144,34 @@ static final class Factory { private final ProductionBinding.Factory productionBindingFactory; private final MultibindingDeclaration.Factory multibindingDeclarationFactory; private final DelegateDeclaration.Factory bindingDelegateDeclarationFactory; + private final SubcomponentDeclaration.Factory subcomponentDeclarationFactory; + private final OptionalBindingDeclaration.Factory optionalBindingDeclarationFactory; Factory( Elements elements, ProvisionBinding.Factory provisionBindingFactory, ProductionBinding.Factory productionBindingFactory, MultibindingDeclaration.Factory multibindingDeclarationFactory, - DelegateDeclaration.Factory bindingDelegateDeclarationFactory) { + DelegateDeclaration.Factory bindingDelegateDeclarationFactory, + SubcomponentDeclaration.Factory subcomponentDeclarationFactory, + OptionalBindingDeclaration.Factory optionalBindingDeclarationFactory) { this.elements = elements; this.provisionBindingFactory = provisionBindingFactory; this.productionBindingFactory = productionBindingFactory; this.multibindingDeclarationFactory = multibindingDeclarationFactory; this.bindingDelegateDeclarationFactory = bindingDelegateDeclarationFactory; + this.subcomponentDeclarationFactory = subcomponentDeclarationFactory; + this.optionalBindingDeclarationFactory = optionalBindingDeclarationFactory; } ModuleDescriptor create(TypeElement moduleElement) { - checkState(getModuleAnnotation(moduleElement).isPresent(), - "%s did not have an AnnotationMirror for @Module", - moduleElement.getQualifiedName()); - ImmutableSet.Builder bindings = ImmutableSet.builder(); ImmutableSet.Builder delegates = ImmutableSet.builder(); ImmutableSet.Builder multibindingDeclarations = ImmutableSet.builder(); + ImmutableSet.Builder optionalDeclarations = + ImmutableSet.builder(); + for (ExecutableElement moduleMethod : methodsIn(elements.getAllMembers(moduleElement))) { if (isAnnotationPresent(moduleMethod, Provides.class)) { bindings.add(provisionBindingFactory.forProvidesMethod(moduleMethod, moduleElement)); @@ -174,6 +186,10 @@ ModuleDescriptor create(TypeElement moduleElement) { multibindingDeclarations.add( multibindingDeclarationFactory.forMultibindsMethod(moduleMethod, moduleElement)); } + if (isAnnotationPresent(moduleMethod, BindsOptionalOf.class)) { + optionalDeclarations.add( + optionalBindingDeclarationFactory.forMethod(moduleMethod, moduleElement)); + } } for (TypeElement memberType : typesIn(elements.getAllMembers(moduleElement))) { @@ -189,12 +205,9 @@ ModuleDescriptor create(TypeElement moduleElement) { collectIncludedModules(new LinkedHashSet(), moduleElement)), bindings.build(), multibindingDeclarations.build(), - delegates.build()); - } - - private static Optional getModuleAnnotation(TypeElement moduleElement) { - return getAnnotationMirror(moduleElement, Module.class) - .or(getAnnotationMirror(moduleElement, ProducerModule.class)); + subcomponentDeclarationFactory.forModule(moduleElement), + delegates.build(), + optionalDeclarations.build()); } @CanIgnoreReturnValue diff --git a/compiler/src/main/java/dagger/internal/codegen/ModuleProcessingStep.java b/compiler/src/main/java/dagger/internal/codegen/ModuleProcessingStep.java index b7b94d67d7f..818e5f510ec 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ModuleProcessingStep.java +++ b/compiler/src/main/java/dagger/internal/codegen/ModuleProcessingStep.java @@ -44,8 +44,8 @@ final class ModuleProcessingStep implements ProcessingStep { /** - * A {@link ProcessingStep} for {@code @Module} classes that generates factories for {@code - * @Provides} methods. + * A {@link ProcessingStep} for {@code @Module} classes that generates factories for + * {@code @Provides} methods. */ static ModuleProcessingStep moduleProcessingStep( Messager messager, @@ -54,14 +54,19 @@ static ModuleProcessingStep moduleProcessingStep( FactoryGenerator factoryGenerator, ProvidesMethodValidator providesMethodValidator, BindsMethodValidator bindsMethodValidator, - MultibindsMethodValidator multibindsMethodValidator) { + MultibindsMethodValidator multibindsMethodValidator, + BindsOptionalOfMethodValidator bindsOptionalOfMethodValidator) { return new ModuleProcessingStep( messager, Module.class, moduleValidator, ImmutableSet.of( new ProvisionModuleMethodFactoryGenerator(provisionBindingFactory, factoryGenerator)), - ImmutableSet.of(providesMethodValidator, bindsMethodValidator, multibindsMethodValidator)); + ImmutableSet.of( + providesMethodValidator, + bindsMethodValidator, + multibindsMethodValidator, + bindsOptionalOfMethodValidator)); } /** @@ -78,7 +83,8 @@ static ModuleProcessingStep producerModuleProcessingStep( ProducerFactoryGenerator producerFactoryGenerator, ProducesMethodValidator producesMethodValidator, BindsMethodValidator bindsMethodValidator, - MultibindsMethodValidator multibindsMethodValidator) { + MultibindsMethodValidator multibindsMethodValidator, + BindsOptionalOfMethodValidator bindsOptionalOfMethodValidator) { return new ModuleProcessingStep( messager, ProducerModule.class, @@ -91,7 +97,8 @@ static ModuleProcessingStep producerModuleProcessingStep( providesMethodValidator, producesMethodValidator, bindsMethodValidator, - multibindsMethodValidator)); + multibindsMethodValidator, + bindsOptionalOfMethodValidator)); } private final Messager messager; diff --git a/compiler/src/main/java/dagger/internal/codegen/ModuleValidator.java b/compiler/src/main/java/dagger/internal/codegen/ModuleValidator.java index 41ce7a874d3..ea58b701334 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ModuleValidator.java +++ b/compiler/src/main/java/dagger/internal/codegen/ModuleValidator.java @@ -23,10 +23,15 @@ import static com.google.auto.common.Visibility.effectiveVisibilityOfElement; import static com.google.common.collect.Iterables.any; import static dagger.internal.codegen.ConfigurationAnnotations.getModuleIncludes; +import static dagger.internal.codegen.ConfigurationAnnotations.getModuleSubcomponents; +import static dagger.internal.codegen.ConfigurationAnnotations.getSubcomponentBuilder; import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_WITH_SAME_NAME; import static dagger.internal.codegen.ErrorMessages.INCOMPATIBLE_MODULE_METHODS; import static dagger.internal.codegen.ErrorMessages.METHOD_OVERRIDES_PROVIDES_METHOD; import static dagger.internal.codegen.ErrorMessages.MODULES_WITH_TYPE_PARAMS_MUST_BE_ABSTRACT; +import static dagger.internal.codegen.ErrorMessages.ModuleMessages.moduleSubcomponentsDoesntHaveBuilder; +import static dagger.internal.codegen.ErrorMessages.ModuleMessages.moduleSubcomponentsIncludesBuilder; +import static dagger.internal.codegen.ErrorMessages.ModuleMessages.moduleSubcomponentsIncludesNonSubcomponent; import static dagger.internal.codegen.ErrorMessages.PROVIDES_METHOD_OVERRIDES_ANOTHER; import static dagger.internal.codegen.ErrorMessages.REFERENCED_MODULE_MUST_NOT_HAVE_TYPE_PARAMS; import static dagger.internal.codegen.ErrorMessages.REFERENCED_MODULE_NOT_ANNOTATED; @@ -35,6 +40,7 @@ import static javax.lang.model.element.Modifier.STATIC; import com.google.auto.common.MoreElements; +import com.google.auto.common.MoreTypes; import com.google.auto.common.Visibility; import com.google.common.base.Function; import com.google.common.base.Joiner; @@ -47,8 +53,10 @@ import com.google.common.collect.Sets; import dagger.Binds; import dagger.Module; +import dagger.Subcomponent; import dagger.multibindings.Multibinds; import dagger.producers.ProducerModule; +import dagger.producers.ProductionSubcomponent; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.EnumSet; @@ -74,6 +82,11 @@ * @since 2.0 */ final class ModuleValidator { + private static final ImmutableSet> SUBCOMPONENT_TYPES = + ImmutableSet.of(Subcomponent.class, ProductionSubcomponent.class); + private static final ImmutableSet> SUBCOMPONENT_BUILDER_TYPES = + ImmutableSet.of(Subcomponent.Builder.class, ProductionSubcomponent.Builder.class); + private final Types types; private final Elements elements; private final MethodSignatureFormatter methodSignatureFormatter; @@ -121,11 +134,60 @@ ValidationReport validate(final TypeElement subject) { } validateModifiers(subject, builder); validateReferencedModules(subject, moduleKind, builder); + validateReferencedSubcomponents(subject, moduleKind, builder); // TODO(gak): port the dagger 1 module validation? return builder.build(); } + private void validateReferencedSubcomponents( + final TypeElement subject, + ModuleDescriptor.Kind moduleKind, + final ValidationReport.Builder builder) { + final AnnotationMirror moduleAnnotation = moduleKind.getModuleAnnotationMirror(subject).get(); + // TODO(ronshapiro): use validateTypesAreDeclared when it is checked in + for (TypeMirror subcomponentAttribute : getModuleSubcomponents(moduleAnnotation)) { + subcomponentAttribute.accept( + new SimpleTypeVisitor6(){ + @Override + protected Void defaultAction(TypeMirror e, Void aVoid) { + builder.addError(e + " is not a valid subcomponent type", subject, moduleAnnotation); + return null; + } + + @Override + public Void visitDeclared(DeclaredType declaredType, Void aVoid) { + TypeElement attributeType = MoreTypes.asTypeElement(declaredType); + if (isAnyAnnotationPresent(attributeType, SUBCOMPONENT_TYPES)) { + validateSubcomponentHasBuilder(attributeType, moduleAnnotation, builder); + } else { + builder.addError( + isAnyAnnotationPresent(attributeType, SUBCOMPONENT_BUILDER_TYPES) + ? moduleSubcomponentsIncludesBuilder(attributeType) + : moduleSubcomponentsIncludesNonSubcomponent(attributeType), + attributeType, + moduleAnnotation); + } + + return null; + } + }, null); + } + } + + private void validateSubcomponentHasBuilder( + TypeElement subcomponentAttribute, + AnnotationMirror moduleAnnotation, + ValidationReport.Builder builder) { + if (getSubcomponentBuilder(subcomponentAttribute).isPresent()) { + return; + } + builder.addError( + moduleSubcomponentsDoesntHaveBuilder(subcomponentAttribute, moduleAnnotation), + subcomponentAttribute, + moduleAnnotation); + } + enum ModuleMethodKind { ABSTRACT_DECLARATION, INSTANCE_BINDING, @@ -174,7 +236,7 @@ private void validateReferencedModules( ModuleDescriptor.Kind moduleKind, ValidationReport.Builder builder) { // Validate that all the modules we include are valid for inclusion. - AnnotationMirror mirror = getAnnotationMirror(subject, moduleKind.moduleAnnotation()).get(); + AnnotationMirror mirror = moduleKind.getModuleAnnotationMirror(subject).get(); ImmutableList includes = getModuleIncludes(mirror); validateReferencedModules(subject, builder, includes, ImmutableSet.of(moduleKind)); } diff --git a/compiler/src/main/java/dagger/internal/codegen/MoreAnnotationMirrors.java b/compiler/src/main/java/dagger/internal/codegen/MoreAnnotationMirrors.java index ce211e687c5..29f41748d28 100644 --- a/compiler/src/main/java/dagger/internal/codegen/MoreAnnotationMirrors.java +++ b/compiler/src/main/java/dagger/internal/codegen/MoreAnnotationMirrors.java @@ -20,6 +20,7 @@ import com.google.common.base.Equivalence; import com.google.common.base.Optional; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Name; /** * A utility class for working with {@link AnnotationMirror} instances, similar to {@link @@ -50,4 +51,8 @@ static Optional unwrapOptionalEquivalence( ? Optional.of(wrappedOptional.get().get()) : Optional.absent(); } + + static Name simpleName(AnnotationMirror annotationMirror) { + return annotationMirror.getAnnotationType().asElement().getSimpleName(); + } } diff --git a/compiler/src/main/java/dagger/internal/codegen/MultibindsMethodValidator.java b/compiler/src/main/java/dagger/internal/codegen/MultibindsMethodValidator.java index 9dfb08a321d..fc0ace9ff3e 100644 --- a/compiler/src/main/java/dagger/internal/codegen/MultibindsMethodValidator.java +++ b/compiler/src/main/java/dagger/internal/codegen/MultibindsMethodValidator.java @@ -17,25 +17,19 @@ package dagger.internal.codegen; import static dagger.internal.codegen.BindingMethodValidator.Abstractness.MUST_BE_ABSTRACT; +import static dagger.internal.codegen.BindingMethodValidator.AllowsMultibindings.NO_MULTIBINDINGS; import static dagger.internal.codegen.BindingMethodValidator.ExceptionSuperclass.NO_EXCEPTIONS; import static dagger.internal.codegen.ErrorMessages.MultibindsMessages.METHOD_MUST_RETURN_MAP_OR_SET; -import static dagger.internal.codegen.ErrorMessages.MultibindsMessages.NO_MAP_KEY; import static dagger.internal.codegen.ErrorMessages.MultibindsMessages.PARAMETERS; import static dagger.internal.codegen.FrameworkTypes.isFrameworkType; -import static dagger.internal.codegen.MapKeys.getMapKeys; import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableSet; -import dagger.MapKey; import dagger.Module; import dagger.Multibindings; -import dagger.multibindings.ElementsIntoSet; -import dagger.multibindings.IntoMap; -import dagger.multibindings.IntoSet; import dagger.multibindings.Multibinds; import dagger.producers.ProducerModule; import java.lang.annotation.Annotation; -import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; @@ -63,7 +57,8 @@ protected MultibindsMethodValidator( methodAnnotation, enclosingElementAnnotations, MUST_BE_ABSTRACT, - NO_EXCEPTIONS); + NO_EXCEPTIONS, + NO_MULTIBINDINGS); } @Override @@ -88,25 +83,6 @@ protected void checkReturnType(ValidationReport.Builder build } } - /** Adds an error if the method has any {@link MapKey @MapKey} annotations. */ - @Override - protected void checkMapKeys(ValidationReport.Builder builder) { - ImmutableSet mapKeys = getMapKeys(builder.getSubject()); - if (!mapKeys.isEmpty()) { - builder.addError(formatErrorMessage(NO_MAP_KEY)); - } - } - - /** - * {@link MultibindingAnnotationsProcessingStep} reports an error if {@link IntoMap @IntoMap}, - * {@link IntoSet @IntoSet}, or {@link ElementsIntoSet @ElementsIntoSet} are applied to the method - * at all, so no need to check again. - */ - @Override - protected void checkMultibindings(ValidationReport.Builder builder) { - // no-op - } - private boolean isPlainMap(TypeMirror returnType) { if (!MapType.isMap(returnType)) { return false; diff --git a/compiler/src/main/java/dagger/internal/codegen/OptionalBindingDeclaration.java b/compiler/src/main/java/dagger/internal/codegen/OptionalBindingDeclaration.java new file mode 100644 index 00000000000..f5c14175f1a --- /dev/null +++ b/compiler/src/main/java/dagger/internal/codegen/OptionalBindingDeclaration.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 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.auto.common.MoreElements.isAnnotationPresent; +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; +import dagger.BindsOptionalOf; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + +/** A {@link BindsOptionalOf} declaration. */ +@AutoValue +abstract class OptionalBindingDeclaration extends BindingDeclaration { + + /** + * {@inheritDoc} + * + *

The key's type is the method's return type, even though the synthetic bindings will be for + * {@code Optional} of derived types. + */ + @Override + public abstract Key key(); + + static class Factory { + private final Key.Factory keyFactory; + + Factory(Key.Factory keyFactory) { + this.keyFactory = keyFactory; + } + + OptionalBindingDeclaration forMethod(ExecutableElement method, TypeElement contributingModule) { + checkArgument(isAnnotationPresent(method, BindsOptionalOf.class)); + return new AutoValue_OptionalBindingDeclaration( + Optional.of(method), + Optional.of(contributingModule), + keyFactory.forBindsOptionalOfMethod(method, contributingModule)); + } + } +} diff --git a/compiler/src/main/java/dagger/internal/codegen/OptionalFactoryClasses.java b/compiler/src/main/java/dagger/internal/codegen/OptionalFactoryClasses.java new file mode 100644 index 00000000000..c40a725607c --- /dev/null +++ b/compiler/src/main/java/dagger/internal/codegen/OptionalFactoryClasses.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2016 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.base.CaseFormat.UPPER_CAMEL; +import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; +import static com.squareup.javapoet.MethodSpec.constructorBuilder; +import static com.squareup.javapoet.MethodSpec.methodBuilder; +import static com.squareup.javapoet.TypeSpec.classBuilder; +import static dagger.internal.codegen.AnnotationSpecs.SUPPRESS_WARNINGS_RAWTYPES; +import static dagger.internal.codegen.AnnotationSpecs.SUPPRESS_WARNINGS_UNCHECKED; +import static dagger.internal.codegen.TypeNames.lazyOf; +import static dagger.internal.codegen.TypeNames.optionalOf; +import static dagger.internal.codegen.TypeNames.providerOf; +import static javax.lang.model.element.Modifier.FINAL; +import static javax.lang.model.element.Modifier.PRIVATE; +import static javax.lang.model.element.Modifier.PUBLIC; +import static javax.lang.model.element.Modifier.STATIC; + +import com.google.common.base.Optional; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeVariableName; +import dagger.internal.Preconditions; +import javax.inject.Provider; + +/** Factory class specifications for optional bindings. */ +// TODO(dpb): Name classes correctly if a component uses both Guava and JDK Optional. +final class OptionalFactoryClasses { + + /** + * The class specification for a {@link Provider>} that always returns {@code + * Optional.absent()}. + */ + static final TypeSpec ABSENT_FACTORY_CLASS = absentFactoryClass(); + + private static TypeSpec absentFactoryClass() { + String className = "AbsentOptionalFactory"; + TypeVariableName typeVariable = TypeVariableName.get("T"); + + return classBuilder(className) + .addTypeVariable(typeVariable) + .addModifiers(PRIVATE, STATIC, FINAL) + .addSuperinterface(providerOf(optionalOf(typeVariable))) + .addJavadoc("A {@link $T} that returns {$T.absent()}.", Provider.class, Optional.class) + .addField(FieldSpec.builder(Provider.class, "INSTANCE", PRIVATE, STATIC, FINAL) + .addAnnotation(SUPPRESS_WARNINGS_RAWTYPES) + .initializer("new $L()", className) + .build()) + .addMethod( + methodBuilder("instance") + .addModifiers(PRIVATE, STATIC) + .addTypeVariable(typeVariable) + .returns(providerOf(optionalOf(typeVariable))) + .addCode("$L // safe covariant cast\n", SUPPRESS_WARNINGS_UNCHECKED) + .addCode("$1T provider = ($1T) INSTANCE;", providerOf(optionalOf(typeVariable))) + .addCode("return provider;") + .build()) + .addMethod( + methodBuilder("get") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(optionalOf(typeVariable)) + .addCode("return $T.absent();", Optional.class) + .build()) + .build(); + } + + /** + * Returns the class specification for a {@link Provider} that returns a present value. The class + * is generic in {@code T}. + * + *

    + *
  • If {@code optionalRequestKind} is {@link DependencyRequest.Kind#INSTANCE}, the class + * implements {@code Provider>}. + *
  • If {@code optionalRequestKind} is {@link DependencyRequest.Kind#PROVIDER}, the class + * implements {@code Provider>>}. + *
  • If {@code optionalRequestKind} is {@link DependencyRequest.Kind#LAZY}, the class implements + * {@code Provider>>}. + *
  • If {@code optionalRequestKind} is {@link DependencyRequest.Kind#PROVIDER_OF_LAZY}, the + * class implements {@code Provider>>>}. + *
+ * + *

Production requests are not yet supported. + */ + static TypeSpec presentFactoryClass(DependencyRequest.Kind optionalValueKind) { + String factoryClassName = + String.format( + "PresentOptional%sFactory", + UPPER_UNDERSCORE.to(UPPER_CAMEL, optionalValueKind.toString())); + + TypeVariableName typeVariable = TypeVariableName.get("T"); + + FieldSpec providerField = + FieldSpec.builder(providerOf(typeVariable), "provider", PRIVATE, FINAL).build(); + + ParameterSpec providerParameter = + ParameterSpec.builder(providerOf(typeVariable), "provider").build(); + + ParameterizedTypeName optionalType = optionalType(optionalValueKind, typeVariable); + + return classBuilder(factoryClassName) + .addTypeVariable(typeVariable) + .addModifiers(PRIVATE, STATIC, FINAL) + .addSuperinterface(providerOf(optionalType)) + .addJavadoc( + "A {@link $T} that returns an {@code $T} using a {@code Provider}.", + Provider.class, + optionalType) + .addField(providerField) + .addMethod( + constructorBuilder() + .addModifiers(PRIVATE) + .addParameter(providerParameter) + .addCode( + "this.$N = $T.checkNotNull($N);", + FieldSpec.builder(providerOf(typeVariable), "provider", PRIVATE, FINAL).build(), + Preconditions.class, + providerParameter) + .build()) + .addMethod( + methodBuilder("get") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(optionalType) + .addCode( + "return $T.of($L);", + Optional.class, + FrameworkType.PROVIDER.to(optionalValueKind, CodeBlock.of("$N", providerField))) + .build()) + .addMethod( + methodBuilder("of") + .addModifiers(PRIVATE, STATIC) + .addTypeVariable(typeVariable) + .returns(providerOf(optionalType)) + .addParameter(providerParameter) + .addCode("return new $L($N);", factoryClassName, providerParameter) + .build()) + .build(); + } + + private static ParameterizedTypeName optionalType( + DependencyRequest.Kind optionalValueKind, TypeName valueType) { + switch (optionalValueKind) { + case INSTANCE: + return optionalOf(valueType); + + case LAZY: + return optionalOf(lazyOf(valueType)); + + case PROVIDER: + return optionalOf(providerOf(valueType)); + + case PROVIDER_OF_LAZY: + return optionalOf(providerOf(lazyOf(valueType))); + + default: + throw new AssertionError(optionalValueKind); + } + } +} diff --git a/compiler/src/main/java/dagger/internal/codegen/OptionalType.java b/compiler/src/main/java/dagger/internal/codegen/OptionalType.java new file mode 100644 index 00000000000..d7e23b17c39 --- /dev/null +++ b/compiler/src/main/java/dagger/internal/codegen/OptionalType.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 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.base.Preconditions.checkArgument; + +import com.google.auto.common.MoreElements; +import com.google.auto.common.MoreTypes; +import com.google.auto.value.AutoValue; +import com.google.common.base.Equivalence; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleTypeVisitor7; + +/** + * Information about an {@code Optional} {@link TypeMirror}. + * + *

Only {@link com.google.common.base.Optional} is supported. + */ +// TODO(dpb): Support java.util.Optional. +@AutoValue +abstract class OptionalType { + + private static final String OPTIONAL_TYPE = "com.google.common.base.Optional"; + + private static final SimpleTypeVisitor7 IS_OPTIONAL = + new SimpleTypeVisitor7(false) { + @Override + public Boolean visitDeclared(DeclaredType t, Void p) { + return MoreElements.asType(t.asElement()).getQualifiedName().contentEquals(OPTIONAL_TYPE); + } + }; + + /** + * The optional type itself, wrapped using {@link MoreTypes#equivalence()}. + * + * @deprecated Use {@link #declaredOptionalType()} instead. + */ + @Deprecated + protected abstract Equivalence.Wrapper wrappedDeclaredOptionalType(); + + /** The optional type itself. */ + @SuppressWarnings("deprecation") + DeclaredType declaredOptionalType() { + return wrappedDeclaredOptionalType().get(); + } + + /** The value type. */ + TypeMirror valueType() { + return declaredOptionalType().getTypeArguments().get(0); + } + + /** Returns {@code true} if {@code type} is an {@code Optional} type. */ + static boolean isOptional(TypeMirror type) { + return type.accept(IS_OPTIONAL, null); + } + + /** Returns {@code true} if {@code key.type()} is an {@code Optional} type. */ + static boolean isOptional(Key key) { + return isOptional(key.type()); + } + + /** + * Returns a {@link OptionalType} for {@code type}. + * + * @throws IllegalArgumentException if {@code type} is not an {@code Optional} type + */ + static OptionalType from(TypeMirror type) { + checkArgument(isOptional(type), "%s must be an Optional", type); + return new AutoValue_OptionalType(MoreTypes.equivalence().wrap(MoreTypes.asDeclared(type))); + } + + /** + * Returns a {@link OptionalType} for {@code key}'s {@link Key#type() type}. + * + * @throws IllegalArgumentException if {@code key.type()} is not an {@code Optional} type + */ + static OptionalType from(Key key) { + return from(key.type()); + } +} diff --git a/compiler/src/main/java/dagger/internal/codegen/ProducerFieldRequestFulfillment.java b/compiler/src/main/java/dagger/internal/codegen/ProducerFieldRequestFulfillment.java index ea1d1d5cf0f..8721106572a 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ProducerFieldRequestFulfillment.java +++ b/compiler/src/main/java/dagger/internal/codegen/ProducerFieldRequestFulfillment.java @@ -21,9 +21,6 @@ import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; -import dagger.MembersInjector; -import dagger.producers.Producer; -import javax.inject.Provider; /** Fulfills requests for {@link ProductionBinding} instances. */ final class ProducerFieldRequestFulfillment extends RequestFulfillment { @@ -38,31 +35,7 @@ final class ProducerFieldRequestFulfillment extends RequestFulfillment { @Override public CodeBlock getSnippetForDependencyRequest( DependencyRequest request, ClassName requestingClass) { - switch (request.kind()) { - case FUTURE: - return CodeBlock.of("$L.get()", producerFieldSelect.getExpressionFor(requestingClass)); - case PRODUCER: - return CodeBlock.of("$L", producerFieldSelect.getExpressionFor(requestingClass)); - case INSTANCE: - case LAZY: - case PRODUCED: - case PROVIDER_OF_LAZY: - throw new IllegalArgumentException( - String.format( - "The framework should never request a %s from a producer: %s", - request.kind(), request)); - case MEMBERS_INJECTOR: - throw new IllegalArgumentException( - String.format( - "Cannot request a %s from a %s", - MembersInjector.class.getSimpleName(), Producer.class.getSimpleName())); - case PROVIDER: - throw new IllegalArgumentException( - String.format( - "Cannot request a %s from a %s", - Provider.class.getSimpleName(), Producer.class.getSimpleName())); - default: - throw new AssertionError(request.kind().toString()); - } + return FrameworkType.PRODUCER.to( + request.kind(), producerFieldSelect.getExpressionFor(requestingClass)); } } diff --git a/compiler/src/main/java/dagger/internal/codegen/ProducesMethodValidator.java b/compiler/src/main/java/dagger/internal/codegen/ProducesMethodValidator.java index aca2f356fed..c51f3a8c483 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ProducesMethodValidator.java +++ b/compiler/src/main/java/dagger/internal/codegen/ProducesMethodValidator.java @@ -18,6 +18,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.BindingMethodValidator.Abstractness.MUST_BE_CONCRETE; +import static dagger.internal.codegen.BindingMethodValidator.AllowsMultibindings.ALLOWS_MULTIBINDINGS; import static dagger.internal.codegen.BindingMethodValidator.ExceptionSuperclass.EXCEPTION; import static dagger.internal.codegen.ErrorMessages.PRODUCES_METHOD_NULLABLE; import static dagger.internal.codegen.ErrorMessages.PRODUCES_METHOD_RAW_FUTURE; @@ -49,7 +50,14 @@ final class ProducesMethodValidator extends BindingMethodValidator { ProducesMethodValidator(Elements elements, Types types) { - super(elements, types, Produces.class, ProducerModule.class, MUST_BE_CONCRETE, EXCEPTION); + super( + elements, + types, + Produces.class, + ProducerModule.class, + MUST_BE_CONCRETE, + EXCEPTION, + ALLOWS_MULTIBINDINGS); } @Override diff --git a/compiler/src/main/java/dagger/internal/codegen/ProviderFieldRequestFulfillment.java b/compiler/src/main/java/dagger/internal/codegen/ProviderFieldRequestFulfillment.java index 0733192e1f1..642b9f74ab7 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ProviderFieldRequestFulfillment.java +++ b/compiler/src/main/java/dagger/internal/codegen/ProviderFieldRequestFulfillment.java @@ -18,16 +18,9 @@ import static com.google.common.base.Preconditions.checkArgument; import static dagger.internal.codegen.BindingKey.Kind.CONTRIBUTION; -import static dagger.internal.codegen.TypeNames.PROVIDER_OF_LAZY; -import com.google.common.util.concurrent.Futures; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; -import dagger.MembersInjector; -import dagger.internal.DoubleCheck; -import dagger.producers.Produced; -import dagger.producers.internal.Producers; -import javax.inject.Provider; /** Fulfills requests for {@link ProvisionBinding} instances. */ final class ProviderFieldRequestFulfillment extends RequestFulfillment { @@ -42,43 +35,7 @@ final class ProviderFieldRequestFulfillment extends RequestFulfillment { @Override public CodeBlock getSnippetForDependencyRequest( DependencyRequest request, ClassName requestingClass) { - switch (request.kind()) { - case FUTURE: - return CodeBlock.of( - "$T.immediateFuture($L.get())", - Futures.class, - providerFieldSelect.getExpressionFor(requestingClass)); - case INSTANCE: - return CodeBlock.of("$L.get()", providerFieldSelect.getExpressionFor(requestingClass)); - case LAZY: - return CodeBlock.of( - "$T.lazy($L)", - DoubleCheck.class, - providerFieldSelect.getExpressionFor(requestingClass)); - case MEMBERS_INJECTOR: - throw new IllegalArgumentException( - String.format( - "Cannot request a %s from a %s", - MembersInjector.class.getSimpleName(), Provider.class.getSimpleName())); - case PRODUCED: - return CodeBlock.of( - "$T.successful($L.get())", - Produced.class, - providerFieldSelect.getExpressionFor(requestingClass)); - case PRODUCER: - return CodeBlock.of( - "$T.producerFromProvider($L)", - Producers.class, - providerFieldSelect.getExpressionFor(requestingClass)); - case PROVIDER: - return CodeBlock.of("$L", providerFieldSelect.getExpressionFor(requestingClass)); - case PROVIDER_OF_LAZY: - return CodeBlock.of( - "$T.create($L)", - PROVIDER_OF_LAZY, - providerFieldSelect.getExpressionFor(requestingClass)); - default: - throw new AssertionError(); - } + return FrameworkType.PROVIDER.to( + request.kind(), providerFieldSelect.getExpressionFor(requestingClass)); } } diff --git a/compiler/src/main/java/dagger/internal/codegen/ProvidesMethodValidator.java b/compiler/src/main/java/dagger/internal/codegen/ProvidesMethodValidator.java index 3c2bd155509..9feadc7a3d8 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ProvidesMethodValidator.java +++ b/compiler/src/main/java/dagger/internal/codegen/ProvidesMethodValidator.java @@ -17,6 +17,7 @@ package dagger.internal.codegen; import static dagger.internal.codegen.BindingMethodValidator.Abstractness.MUST_BE_CONCRETE; +import static dagger.internal.codegen.BindingMethodValidator.AllowsMultibindings.ALLOWS_MULTIBINDINGS; import static dagger.internal.codegen.BindingMethodValidator.ExceptionSuperclass.RUNTIME_EXCEPTION; import static dagger.internal.codegen.ErrorMessages.provisionMayNotDependOnProducerType; @@ -44,7 +45,8 @@ final class ProvidesMethodValidator extends BindingMethodValidator { Provides.class, ImmutableSet.of(Module.class, ProducerModule.class), MUST_BE_CONCRETE, - RUNTIME_EXCEPTION); + RUNTIME_EXCEPTION, + ALLOWS_MULTIBINDINGS); } @Override diff --git a/compiler/src/main/java/dagger/internal/codegen/ProvisionBinding.java b/compiler/src/main/java/dagger/internal/codegen/ProvisionBinding.java index 9dbeb02bdab..b6e459ab918 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ProvisionBinding.java +++ b/compiler/src/main/java/dagger/internal/codegen/ProvisionBinding.java @@ -83,6 +83,8 @@ private static Builder builder() { .dependencies(ImmutableSet.of()); } + abstract Builder toBuilder(); + @AutoValue.Builder @CanIgnoreReturnValue abstract static class Builder extends ContributionBinding.Builder { @@ -276,6 +278,16 @@ ProvisionBinding forSubcomponentBuilderMethod( .build(); } + ProvisionBinding syntheticSubcomponentBuilder( + ImmutableSet subcomponentDeclarations) { + SubcomponentDeclaration subcomponentDeclaration = subcomponentDeclarations.iterator().next(); + return ProvisionBinding.builder() + .contributionType(ContributionType.UNIQUE) + .key(subcomponentDeclaration.key()) + .bindingKind(Kind.SUBCOMPONENT_BUILDER) + .build(); + } + ProvisionBinding delegate( DelegateDeclaration delegateDeclaration, ProvisionBinding delegate) { return delegateBuilder(delegateDeclaration).nullableType(delegate.nullableType()).build(); @@ -300,5 +312,30 @@ private ProvisionBinding.Builder delegateBuilder(DelegateDeclaration delegateDec .bindingKind(Kind.SYNTHETIC_DELEGATE_BINDING) .scope(Scope.uniqueScopeOf(delegateDeclaration.bindingElement().get())); } + + /** + * Returns a synthetic binding for an {@linkplain dagger.BindsOptionalOf optional binding} in a + * component with no binding for the underlying key. + */ + ProvisionBinding syntheticAbsentBinding(Key key) { + return ProvisionBinding.builder() + .contributionType(ContributionType.UNIQUE) + .key(key) + .bindingKind(Kind.SYNTHETIC_OPTIONAL_BINDING) + .build(); + } + + /** + * Returns a synthetic binding for an {@linkplain dagger.BindsOptionalOf optional binding} in a + * component with a binding for the underlying key. + */ + ProvisionBinding syntheticPresentBinding(Key key) { + return syntheticAbsentBinding(key) + .toBuilder() + .dependencies( + dependencyRequestFactory.forSyntheticPresentOptionalBinding( + key, DependencyRequest.Kind.PROVIDER)) + .build(); + } } } diff --git a/compiler/src/main/java/dagger/internal/codegen/ResolvedBindings.java b/compiler/src/main/java/dagger/internal/codegen/ResolvedBindings.java index 95a8cc281a6..d146fe5d9c3 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ResolvedBindings.java +++ b/compiler/src/main/java/dagger/internal/codegen/ResolvedBindings.java @@ -82,7 +82,19 @@ public Key key() { * {@link BindingKey.Kind#CONTRIBUTION}, this is empty. */ abstract ImmutableSet multibindingDeclarations(); - + + /** + * The subcomponent declarations for {@link #bindingKey()}. If {@link #bindingKey()}'s kind is not + * {@link BindingKey.Kind#CONTRIBUTION}, this is empty. + */ + abstract ImmutableSet subcomponentDeclarations(); + + /** + * The optional binding declarations for {@link #bindingKey()}. If {@link #bindingKey()}'s kind is + * not {@link BindingKey.Kind#CONTRIBUTION}, this is empty. + */ + abstract ImmutableSet optionalBindingDeclarations(); + /** * All bindings for {@link #bindingKey()}, regardless of in which component they were resolved. */ @@ -121,10 +133,13 @@ Binding binding() { } /** - * {@code true} if there are no {@link #bindings()} or {@link #multibindingDeclarations()}. + * {@code true} if there are no {@link #bindings()}, {@link #multibindingDeclarations()}, or + * {@link #subcomponentDeclarations()}. */ boolean isEmpty() { - return bindings().isEmpty() && multibindingDeclarations().isEmpty(); + return bindings().isEmpty() + && multibindingDeclarations().isEmpty() + && subcomponentDeclarations().isEmpty(); } /** @@ -189,21 +204,23 @@ Optional ownedMembersInjectionBinding() { return Optional.fromNullable(allMembersInjectionBindings().get(owningComponent())); } - /** - * Creates a {@link ResolvedBindings} for contribution bindings. - */ + /** Creates a {@link ResolvedBindings} for contribution bindings. */ static ResolvedBindings forContributionBindings( BindingKey bindingKey, ComponentDescriptor owningComponent, Multimap contributionBindings, - Iterable multibindings) { + Iterable multibindings, + Iterable subcomponentDeclarations, + Iterable optionalBindingDeclarations) { checkArgument(bindingKey.kind().equals(BindingKey.Kind.CONTRIBUTION)); return new AutoValue_ResolvedBindings( bindingKey, owningComponent, ImmutableSetMultimap.copyOf(contributionBindings), ImmutableMap.of(), - ImmutableSet.copyOf(multibindings)); + ImmutableSet.copyOf(multibindings), + ImmutableSet.copyOf(subcomponentDeclarations), + ImmutableSet.copyOf(optionalBindingDeclarations)); } /** @@ -219,7 +236,9 @@ static ResolvedBindings forMembersInjectionBinding( owningComponent, ImmutableSetMultimap.of(), ImmutableMap.of(owningComponent, ownedMembersInjectionBinding), - ImmutableSet.of()); + ImmutableSet.of(), + ImmutableSet.of(), + ImmutableSet.of()); } /** @@ -231,7 +250,9 @@ static ResolvedBindings noBindings(BindingKey bindingKey, ComponentDescriptor ow owningComponent, ImmutableSetMultimap.of(), ImmutableMap.of(), - ImmutableSet.of()); + ImmutableSet.of(), + ImmutableSet.of(), + ImmutableSet.of()); } /** @@ -244,7 +265,9 @@ ResolvedBindings asInheritedIn(ComponentDescriptor owningComponent) { owningComponent, allContributionBindings(), allMembersInjectionBindings(), - multibindingDeclarations()); + multibindingDeclarations(), + subcomponentDeclarations(), + optionalBindingDeclarations()); } /** @@ -266,15 +289,16 @@ ContributionBinding contributionBinding() { } /** - * The binding type for these bindings. If there are {@link #multibindingDeclarations()} but no - * {@link #bindings()}, returns {@link BindingType#PROVISION}. + * The binding type for these bindings. If there are {@link #multibindingDeclarations()} or {@link + * #subcomponentDeclarations()} but no {@link #bindings()}, returns {@link BindingType#PROVISION}. * * @throws IllegalStateException if {@link #isEmpty()} or the binding types conflict */ @Override public BindingType bindingType() { checkState(!isEmpty(), "empty bindings for %s", bindingKey()); - if (bindings().isEmpty() && !multibindingDeclarations().isEmpty()) { + if (bindings().isEmpty() + && (!multibindingDeclarations().isEmpty() || !subcomponentDeclarations().isEmpty())) { // Only multibinding declarations, so assume provision. return BindingType.PROVISION; } diff --git a/compiler/src/main/java/dagger/internal/codegen/SourceFiles.java b/compiler/src/main/java/dagger/internal/codegen/SourceFiles.java index c5a2e36c4df..5683b38a0a6 100644 --- a/compiler/src/main/java/dagger/internal/codegen/SourceFiles.java +++ b/compiler/src/main/java/dagger/internal/codegen/SourceFiles.java @@ -142,11 +142,11 @@ static CodeBlock frameworkTypeUsageStatement( case PROVIDER: case PRODUCER: case MEMBERS_INJECTOR: - return CodeBlock.of("$L", frameworkTypeMemberSelect); + return frameworkTypeMemberSelect; case PROVIDER_OF_LAZY: return CodeBlock.of("$T.create($L)", PROVIDER_OF_LAZY, frameworkTypeMemberSelect); - default: - throw new AssertionError(); + default: // including PRODUCED + throw new AssertionError(dependencyKind); } } diff --git a/compiler/src/main/java/dagger/internal/codegen/SubcomponentDeclaration.java b/compiler/src/main/java/dagger/internal/codegen/SubcomponentDeclaration.java new file mode 100644 index 00000000000..bdf24222fc3 --- /dev/null +++ b/compiler/src/main/java/dagger/internal/codegen/SubcomponentDeclaration.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 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.auto.common.AnnotationMirrors.getAnnotationElementAndValue; +import static dagger.internal.codegen.ConfigurationAnnotations.getModuleAnnotation; +import static dagger.internal.codegen.ConfigurationAnnotations.getModuleSubcomponents; +import static dagger.internal.codegen.ConfigurationAnnotations.getSubcomponentBuilder; + +import com.google.auto.common.MoreTypes; +import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableSet; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + +/** + * A declaration for a subcomponent that is included in a module via {@link + * dagger.Module#subcomponents()}. + */ +@AutoValue +abstract class SubcomponentDeclaration extends BindingDeclaration { + /** + * Key for the {@link dagger.Subcomponent.Builder} or {@link + * dagger.producers.ProductionSubcomponent.Builder} of {@link #subcomponentType()}. + */ + @Override + public abstract Key key(); + + /** + * The type element that defines the {@link dagger.Subcomponent} or {@link + * dagger.producers.ProductionSubcomponent} for this declaration. + */ + abstract TypeElement subcomponentType(); + + abstract AnnotationMirror moduleAnnotation(); + + static class Factory { + private final Key.Factory keyFactory; + + public Factory(Key.Factory keyFactory) { + this.keyFactory = keyFactory; + } + + ImmutableSet forModule(TypeElement module) { + ImmutableSet.Builder declarations = ImmutableSet.builder(); + AnnotationMirror moduleAnnotation = getModuleAnnotation(module).get(); + ExecutableElement subcomponentAttribute = + getAnnotationElementAndValue(moduleAnnotation, "subcomponents").getKey(); + for (TypeElement subcomponent : + MoreTypes.asTypeElements(getModuleSubcomponents(moduleAnnotation))) { + declarations.add( + new AutoValue_SubcomponentDeclaration( + Optional.of(subcomponentAttribute), + Optional.of(module), + keyFactory.forSubcomponentBuilder( + getSubcomponentBuilder(subcomponent).get().asType()), + subcomponent, + moduleAnnotation)); + } + return declarations.build(); + } + } +} diff --git a/compiler/src/main/java/dagger/internal/codegen/SubcomponentWriter.java b/compiler/src/main/java/dagger/internal/codegen/SubcomponentWriter.java index 5aee50317a2..5c87ab4d562 100644 --- a/compiler/src/main/java/dagger/internal/codegen/SubcomponentWriter.java +++ b/compiler/src/main/java/dagger/internal/codegen/SubcomponentWriter.java @@ -17,6 +17,7 @@ package dagger.internal.codegen; import static com.google.common.base.CaseFormat.LOWER_CAMEL; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; import static com.google.common.collect.Sets.difference; import static com.squareup.javapoet.MethodSpec.methodBuilder; @@ -40,10 +41,10 @@ import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import dagger.internal.Preconditions; -import dagger.internal.codegen.ComponentDescriptor.BuilderSpec; +import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor; +import dagger.internal.codegen.DependencyRequest.Kind; import java.util.List; import java.util.Set; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ExecutableType; @@ -54,12 +55,17 @@ */ final class SubcomponentWriter extends AbstractComponentWriter { - private AbstractComponentWriter parent; - private ExecutableElement subcomponentFactoryMethod; + private final AbstractComponentWriter parent; - public SubcomponentWriter( + /** + * The parent's factory method to create this subcomponent, or {@link Optional#absent()} if the + * subcomponent was added via {@link dagger.Module#subcomponents()}. + */ + private final Optional subcomponentFactoryMethod; + + SubcomponentWriter( AbstractComponentWriter parent, - ExecutableElement subcomponentFactoryMethod, + Optional subcomponentFactoryMethod, BindingGraph subgraph) { super( parent.types, @@ -100,10 +106,15 @@ protected MemberSelect getMemberSelect(BindingKey key) { } private ExecutableType resolvedSubcomponentFactoryMethod() { + checkState( + subcomponentFactoryMethod.isPresent(), + "%s does not have a factory method for %s", + parent.componentDefinitionType(), + componentDefinitionType()); return MoreTypes.asExecutable( types.asMemberOf( MoreTypes.asDeclared(parent.componentDefinitionType().asType()), - subcomponentFactoryMethod)); + subcomponentFactoryMethod.get().methodElement())); } @Override @@ -146,27 +157,32 @@ protected void addBuilderClass(TypeSpec builder) { @Override protected void addFactoryMethods() { + if (!subcomponentFactoryMethod.isPresent() + || !subcomponentFactoryMethod.get().kind().isSubcomponentKind()) { + // subcomponent builder methods are implemented in + // AbstractComponentWriter.implementInterfaceMethods + return; + } MethodSpec.Builder componentMethod = - methodBuilder(subcomponentFactoryMethod.getSimpleName().toString()) + methodBuilder(subcomponentFactoryMethod.get().methodElement().getSimpleName().toString()) .addModifiers(PUBLIC) .addAnnotation(Override.class); - if (graph.componentDescriptor().builderSpec().isPresent()) { - BuilderSpec spec = graph.componentDescriptor().builderSpec().get(); - componentMethod - .returns(ClassName.get(spec.builderDefinitionType())) - .addStatement("return new $T()", builderName.get()); - } else { - ExecutableType resolvedMethod = resolvedSubcomponentFactoryMethod(); - componentMethod.returns(ClassName.get(resolvedMethod.getReturnType())); - writeSubcomponentWithoutBuilder(componentMethod, resolvedMethod); - } + ExecutableType resolvedMethod = resolvedSubcomponentFactoryMethod(); + componentMethod.returns(ClassName.get(resolvedMethod.getReturnType())); + writeSubcomponentWithoutBuilder(componentMethod, resolvedMethod); parent.component.addMethod(componentMethod.build()); } + @Override + protected TypeSpec optionalFactoryClass(Optional optionalValueKind) { + return parent.optionalFactoryClass(optionalValueKind); + } + private void writeSubcomponentWithoutBuilder( MethodSpec.Builder componentMethod, ExecutableType resolvedMethod) { ImmutableList.Builder subcomponentConstructorParameters = ImmutableList.builder(); - List params = subcomponentFactoryMethod.getParameters(); + List params = + subcomponentFactoryMethod.get().methodElement().getParameters(); List paramTypes = resolvedMethod.getParameterTypes(); for (int i = 0; i < params.size(); i++) { VariableElement moduleVariable = params.get(i); diff --git a/compiler/src/main/java/dagger/internal/codegen/TypeNames.java b/compiler/src/main/java/dagger/internal/codegen/TypeNames.java index 939f15ff162..1e3cb30bab7 100644 --- a/compiler/src/main/java/dagger/internal/codegen/TypeNames.java +++ b/compiler/src/main/java/dagger/internal/codegen/TypeNames.java @@ -16,12 +16,14 @@ package dagger.internal.codegen; +import com.google.common.base.Optional; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; +import dagger.Lazy; import dagger.MembersInjector; import dagger.internal.DelegateFactory; import dagger.internal.DoubleCheck; @@ -61,6 +63,7 @@ final class TypeNames { static final ClassName FUTURES = ClassName.get(Futures.class); static final ClassName ILLEGAL_STATE_EXCEPTION = ClassName.get(IllegalStateException.class); static final ClassName INSTANCE_FACTORY = ClassName.get(InstanceFactory.class); + static final ClassName LAZY = ClassName.get(Lazy.class); static final ClassName LIST = ClassName.get(List.class); static final ClassName LISTENABLE_FUTURE = ClassName.get(ListenableFuture.class); static final ClassName MAP_FACTORY = ClassName.get(MapFactory.class); @@ -70,6 +73,7 @@ final class TypeNames { static final ClassName MAP_PROVIDER_FACTORY = ClassName.get(MapProviderFactory.class); static final ClassName MEMBERS_INJECTOR = ClassName.get(MembersInjector.class); static final ClassName MEMBERS_INJECTORS = ClassName.get(MembersInjectors.class); + static final ClassName OPTIONAL = ClassName.get(Optional.class); static final ClassName PRODUCER_TOKEN = ClassName.get(ProducerToken.class); static final ClassName PRODUCED = ClassName.get(Produced.class); static final ClassName PRODUCER = ClassName.get(Producer.class); @@ -94,36 +98,48 @@ final class TypeNames { static final TypeName SET_OF_FACTORIES = ParameterizedTypeName.get(Set.class, ProductionComponentMonitor.Factory.class); + static ParameterizedTypeName abstractProducerOf(TypeName typeName) { + return ParameterizedTypeName.get(ABSTRACT_PRODUCER, typeName); + } + + static ParameterizedTypeName factoryOf(TypeName factoryType) { + return ParameterizedTypeName.get(FACTORY, factoryType); + } + + static ParameterizedTypeName lazyOf(TypeName typeName) { + return ParameterizedTypeName.get(LAZY, typeName); + } + static ParameterizedTypeName listOf(TypeName typeName) { return ParameterizedTypeName.get(LIST, typeName); } - static ParameterizedTypeName setOf(TypeName elementType) { - return ParameterizedTypeName.get(SET, elementType); + static ParameterizedTypeName listenableFutureOf(TypeName typeName) { + return ParameterizedTypeName.get(LISTENABLE_FUTURE, typeName); } - static ParameterizedTypeName abstractProducerOf(TypeName typeName) { - return ParameterizedTypeName.get(ABSTRACT_PRODUCER, typeName); + static ParameterizedTypeName membersInjectorOf(TypeName membersInjectorType) { + return ParameterizedTypeName.get(MEMBERS_INJECTOR, membersInjectorType); + } + + static ParameterizedTypeName optionalOf(TypeName type) { + return ParameterizedTypeName.get(OPTIONAL, type); } static ParameterizedTypeName producedOf(TypeName typeName) { return ParameterizedTypeName.get(PRODUCED, typeName); } - static ParameterizedTypeName listenableFutureOf(TypeName typeName) { - return ParameterizedTypeName.get(LISTENABLE_FUTURE, typeName); + static ParameterizedTypeName producerOf(TypeName typeName) { + return ParameterizedTypeName.get(PRODUCER, typeName); } static ParameterizedTypeName providerOf(TypeName typeName) { return ParameterizedTypeName.get(PROVIDER, typeName); } - static ParameterizedTypeName membersInjectorOf(TypeName membersInjectorType) { - return ParameterizedTypeName.get(MEMBERS_INJECTOR, membersInjectorType); - } - - static ParameterizedTypeName factoryOf(TypeName factoryType) { - return ParameterizedTypeName.get(FACTORY, factoryType); + static ParameterizedTypeName setOf(TypeName elementType) { + return ParameterizedTypeName.get(SET, elementType); } private TypeNames() {} diff --git a/compiler/src/main/java/dagger/internal/codegen/UnwrappedMapKeyGenerator.java b/compiler/src/main/java/dagger/internal/codegen/UnwrappedMapKeyGenerator.java new file mode 100644 index 00000000000..47a6293a0a0 --- /dev/null +++ b/compiler/src/main/java/dagger/internal/codegen/UnwrappedMapKeyGenerator.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 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 dagger.MapKey; +import java.util.Set; +import javax.annotation.processing.Filer; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; + +/** + * Generates classes that create annotation instances for an unwrapped {@link MapKey} annotation + * type whose nested value is an annotation. The generated class will have a private empty + * constructor and a static method that creates each annotation type that is nested in the top-level + * annotation type. + * + *

So for an example {@link MapKey} annotation: + * + *

+ *   {@literal @MapKey}(unwrapValue = true)
+ *   {@literal @interface} Foo {
+ *     Bar bar();
+ *   }
+ *
+ *   {@literal @interface} Bar {
+ *     Class baz();
+ *   }
+ * 
+ * + * the generated class will look like: + * + *
+ *   public final class FooCreator {
+ *     private FooCreator() {}
+ *
+ *     public static Bar createBar(Class baz) { … }
+ *   }
+ * 
+ */ +final class UnwrappedMapKeyGenerator extends AnnotationCreatorGenerator { + + UnwrappedMapKeyGenerator(Filer filer, Elements elements) { + super(filer, elements); + } + + @Override + protected Set annotationsToCreate(TypeElement annotationElement) { + Set nestedAnnotationElements = super.annotationsToCreate(annotationElement); + nestedAnnotationElements.remove(annotationElement); + return nestedAnnotationElements; + } +} diff --git a/compiler/src/main/java/dagger/internal/codegen/Util.java b/compiler/src/main/java/dagger/internal/codegen/Util.java index 234b1f52834..2637f419666 100644 --- a/compiler/src/main/java/dagger/internal/codegen/Util.java +++ b/compiler/src/main/java/dagger/internal/codegen/Util.java @@ -30,6 +30,7 @@ import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import dagger.Binds; import dagger.Provides; @@ -189,6 +190,15 @@ static boolean isAnyAnnotationPresent( return false; } + @SafeVarargs + static boolean isAnyAnnotationPresent( + Element element, + Class first, + Class... otherAnnotations) { + return isAnnotationPresent(element, first) + || isAnyAnnotationPresent(element, ImmutableList.copyOf(otherAnnotations)); + } + /** * The elements in {@code elements} that are annotated with an annotation of type * {@code annotation}. diff --git a/compiler/src/test/java/dagger/internal/codegen/BindsOptionalOfMethodValidatorTest.java b/compiler/src/test/java/dagger/internal/codegen/BindsOptionalOfMethodValidatorTest.java new file mode 100644 index 00000000000..9f48dd308ee --- /dev/null +++ b/compiler/src/test/java/dagger/internal/codegen/BindsOptionalOfMethodValidatorTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2016 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 dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass; +import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatModuleMethod; + +import com.google.common.collect.ImmutableList; +import dagger.Module; +import dagger.producers.ProducerModule; +import java.lang.annotation.Annotation; +import java.util.Collection; +import javax.inject.Inject; +import javax.inject.Qualifier; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** Tests {@link BindsOptionalOfMethodValidator}. */ +@RunWith(Parameterized.class) +public class BindsOptionalOfMethodValidatorTest { + @Parameters(name = "{0}") + public static Collection data() { + return ImmutableList.copyOf(new Object[][] {{Module.class}, {ProducerModule.class}}); + } + + private final String moduleDeclaration; + + public BindsOptionalOfMethodValidatorTest(Class moduleAnnotation) { + moduleDeclaration = "@" + moduleAnnotation.getCanonicalName() + " abstract class %s { %s }"; + } + + @Test + public void nonAbstract() { + assertThatMethod("@BindsOptionalOf Object concrete() { return null; }") + .hasError("must be abstract"); + } + + @Test + public void hasParameters() { + assertThatMethod("@BindsOptionalOf abstract Object hasParameters(String s1);") + .hasError("parameters"); + } + + @Test + public void typeParameters() { + assertThatMethod("@BindsOptionalOf abstract S generic();").hasError("type parameters"); + } + + @Test + public void notInModule() { + assertThatMethodInUnannotatedClass("@BindsOptionalOf abstract Object notInModule();") + .hasError("within a @Module or @ProducerModule"); + } + + @Test + public void throwsException() { + assertThatMethod("@BindsOptionalOf abstract Object throwsException() throws RuntimeException;") + .hasError("may not throw"); + } + + @Test + public void returnsVoid() { + assertThatMethod("@BindsOptionalOf abstract void returnsVoid();").hasError("void"); + } + + @Test + public void returnsMembersInjector() { + assertThatMethod("@BindsOptionalOf abstract MembersInjector returnsMembersInjector();") + .hasError("framework"); + } + + @Test + public void tooManyQualifiers() { + assertThatMethod( + "@BindsOptionalOf @Qualifier1 @Qualifier2 abstract String tooManyQualifiers();") + .importing(Qualifier1.class, Qualifier2.class) + .hasError("more than one @Qualifier"); + } + + @Test + public void intoSet() { + assertThatMethod("@BindsOptionalOf @IntoSet abstract String intoSet();") + .hasError("Multibinding annotations"); + } + + @Test + public void elementsIntoSet() { + assertThatMethod("@BindsOptionalOf @ElementsIntoSet abstract Set elementsIntoSet();") + .hasError("Multibinding annotations"); + } + + @Test + public void intoMap() { + assertThatMethod("@BindsOptionalOf @IntoMap abstract String intoMap();") + .hasError("Multibinding annotations"); + } + + /** An injectable value object. */ + public static final class Thing { + @Inject + Thing() {} + } + + @Test + public void implicitlyProvidedType() { + assertThatMethod("@BindsOptionalOf abstract Thing thing();") + .importing(Thing.class) + .hasError("return unqualified types that have an @Inject-annotated constructor"); + } + + private DaggerModuleMethodSubject assertThatMethod(String method) { + return assertThatModuleMethod(method).withDeclaration(moduleDeclaration); + } + + /** A qualifier. */ + @Qualifier + public @interface Qualifier1 {} + + /** A qualifier. */ + @Qualifier + public @interface Qualifier2 {} +} diff --git a/compiler/src/test/java/dagger/internal/codegen/ComponentProcessorTest.java b/compiler/src/test/java/dagger/internal/codegen/ComponentProcessorTest.java index be07f2366fa..98049a7f4de 100644 --- a/compiler/src/test/java/dagger/internal/codegen/ComponentProcessorTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/ComponentProcessorTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertAbout; import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; +import static com.google.testing.compile.JavaSourcesSubject.assertThat; import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; import static dagger.internal.codegen.GeneratedLines.GENERATED_ANNOTATION; import static java.util.Arrays.asList; @@ -1027,7 +1028,101 @@ public void subcomponentOmitsInheritedBindings() { .generatesSources(expected); } - @Test public void testDefaultPackage() { + @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 generatedComponentWithoutSubcomponent = + JavaFileObjects.forSourceLines( + "test.DaggerParent", + "package test;", + "", + "import dagger.internal.Preconditions;", + "import javax.annotation.Generated;", + "", + GENERATED_ANNOTATION, + "public final class DaggerParent implements Parent {", + "", + " private DaggerParent(Builder builder) {", + " assert builder != null;", + " }", + "", + " public static Builder builder() {", + " return new Builder();", + " }", + "", + " public static Parent create() {", + " return builder().build();", + " }", + "", + " @Override", + " public String notSubcomponent() {", + " return ParentModule_NotSubcomponentFactory.create().get();", + " }", + "", + " public static final class Builder {", + "", + " private Builder() {", + " }", + "", + " public Parent build() {", + " return new DaggerParent(this);", + " }", + "", + " @Deprecated", + " public Builder parentModule(ParentModule parentModule) {", + " Preconditions.checkNotNull(parentModule);", + " return this;", + " }", + " }", + "}"); + + assertThat(component, module, subcomponent) + .processedWith(new ComponentProcessor()) + .compilesWithoutError() + .and() + .generatesSources(generatedComponentWithoutSubcomponent); + } + + @Test + public void testDefaultPackage() { JavaFileObject aClass = JavaFileObjects.forSourceLines("AClass", "class AClass {}"); JavaFileObject bClass = JavaFileObjects.forSourceLines("BClass", "import javax.inject.Inject;", @@ -2389,6 +2484,102 @@ public void attemptToInjectWildcardGenerics() { } @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 javax.annotation.Generated;", + "", + GENERATED_ANNOTATION, + "public final class DaggerParent implements Parent {", + " private DaggerParent(Builder builder) {", + " assert builder != null;", + " }", + "", + " public static Builder builder() {", + " return new Builder();", + " }", + "", + " public static Parent create() {", + " return builder().build();", + " }", + "", + " public static final class Builder {", + " private Builder() {}", + "", + " public Parent build() {", + " return new DaggerParent(this);", + " }", + "", + " @Deprecated", + " public Builder testModule(TestModule testModule) {", + " Preconditions.checkNotNull(testModule);", + " return this;", + " }", + " }", + "}"); + + assertThat(foo, module, component, prunedSubcomponent) + .processedWith(new ComponentProcessor()) + .compilesWithoutError() + .and() + .generatesSources(generated); + } + public void invalidComponentDependencies() { JavaFileObject testComponent = JavaFileObjects.forSourceLines( diff --git a/compiler/src/test/java/dagger/internal/codegen/GraphValidationTest.java b/compiler/src/test/java/dagger/internal/codegen/GraphValidationTest.java index 701f56d64d6..ba1e626f8c7 100644 --- a/compiler/src/test/java/dagger/internal/codegen/GraphValidationTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/GraphValidationTest.java @@ -1424,6 +1424,190 @@ public void bindsMissingRightHandSide() { .compilesWithoutError(); } + @Test + public void nullCheckForOptionalInstance() { + JavaFileObject a = + JavaFileObjects.forSourceLines( + "test.A", + "package test;", + "", + "import com.google.common.base.Optional;", + "import javax.inject.Inject;", + "", + "final class A {", + " @Inject A(Optional optional) {}", + "}"); + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.BindsOptionalOf;", + "import dagger.Provides;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "abstract class TestModule {", + " @Nullable @Provides static String provideString() { return null; }", + " @BindsOptionalOf abstract String optionalString();", + "}"); + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " A a();", + "}"); + assertAbout(javaSources()) + .that(ImmutableList.of(NULLABLE, a, module, component)) + .processedWith(new ComponentProcessor()) + .failsToCompile() + .withErrorContaining( + nullableToNonNullable( + "java.lang.String", + "@test.Nullable @Provides String test.TestModule.provideString()")); + } + + @Test + public void nullCheckForOptionalProvider() { + JavaFileObject a = + JavaFileObjects.forSourceLines( + "test.A", + "package test;", + "", + "import com.google.common.base.Optional;", + "import javax.inject.Inject;", + "import javax.inject.Provider;", + "", + "final class A {", + " @Inject A(Optional> optional) {}", + "}"); + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.BindsOptionalOf;", + "import dagger.Provides;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "abstract class TestModule {", + " @Nullable @Provides static String provideString() { return null; }", + " @BindsOptionalOf abstract String optionalString();", + "}"); + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " A a();", + "}"); + assertAbout(javaSources()) + .that(ImmutableList.of(NULLABLE, a, module, component)) + .processedWith(new ComponentProcessor()) + .compilesWithoutError(); + } + + @Test + public void nullCheckForOptionalLazy() { + JavaFileObject a = + JavaFileObjects.forSourceLines( + "test.A", + "package test;", + "", + "import com.google.common.base.Optional;", + "import dagger.Lazy;", + "import javax.inject.Inject;", + "", + "final class A {", + " @Inject A(Optional> optional) {}", + "}"); + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.BindsOptionalOf;", + "import dagger.Provides;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "abstract class TestModule {", + " @Nullable @Provides static String provideString() { return null; }", + " @BindsOptionalOf abstract String optionalString();", + "}"); + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " A a();", + "}"); + assertAbout(javaSources()) + .that(ImmutableList.of(NULLABLE, a, module, component)) + .processedWith(new ComponentProcessor()) + .compilesWithoutError(); + } + + @Test + public void nullCheckForOptionalProviderOfLazy() { + JavaFileObject a = + JavaFileObjects.forSourceLines( + "test.A", + "package test;", + "", + "import com.google.common.base.Optional;", + "import dagger.Lazy;", + "import javax.inject.Inject;", + "import javax.inject.Provider;", + "", + "final class A {", + " @Inject A(Optional>> optional) {}", + "}"); + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.BindsOptionalOf;", + "import dagger.Provides;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "abstract class TestModule {", + " @Nullable @Provides static String provideString() { return null; }", + " @BindsOptionalOf abstract String optionalString();", + "}"); + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " A a();", + "}"); + assertAbout(javaSources()) + .that(ImmutableList.of(NULLABLE, a, module, component)) + .processedWith(new ComponentProcessor()) + .compilesWithoutError(); + } + @Test public void componentDependencyMustNotCycle_Direct() { JavaFileObject shortLifetime = JavaFileObjects.forSourceLines("test.ComponentShort", "package test;", diff --git a/compiler/src/test/java/dagger/internal/codegen/ModuleValidatorTest.java b/compiler/src/test/java/dagger/internal/codegen/ModuleValidatorTest.java new file mode 100644 index 00000000000..9d52862d909 --- /dev/null +++ b/compiler/src/test/java/dagger/internal/codegen/ModuleValidatorTest.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2016 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.JavaSourcesSubject.assertThat; + +import com.google.testing.compile.JavaFileObjects; +import dagger.Module; +import dagger.producers.ProducerModule; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public final class ModuleValidatorTest { + + @Parameterized.Parameters + public static Collection parameters() { + return Arrays.asList(new Object[][] {{ModuleType.MODULE}, {ModuleType.PRODUCER_MODULE}}); + } + + private enum ModuleType { + MODULE(Module.class), + PRODUCER_MODULE(ProducerModule.class), + ; + + private final Class annotation; + + ModuleType(Class annotation) { + this.annotation = annotation; + } + + String annotationWithSubcomponent(String subcomponent) { + return String.format("@%s(subcomponents = %s)", annotation.getSimpleName(), subcomponent); + } + + String importStatement() { + return String.format("import %s;", annotation.getName()); + } + + String simpleName() { + return annotation.getSimpleName(); + } + } + + private final ModuleType moduleType; + + public ModuleValidatorTest(ModuleType moduleType) { + this.moduleType = moduleType; + } + + @Test + public void moduleSubcomponents_notASubcomponent() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + moduleType.importStatement(), + "", + moduleType.annotationWithSubcomponent("NotASubcomponent.class"), + "class TestModule {}"); + JavaFileObject notASubcomponent = + JavaFileObjects.forSourceLines( + "test.NotASubcomponent", "package test;", "", "class NotASubcomponent {}"); + assertThat(module, notASubcomponent) + .processedWith(new ComponentProcessor()) + .failsToCompile() + .withErrorContaining( + "test.NotASubcomponent is not a @Subcomponent or @ProductionSubcomponent") + .in(module) + .onLine(5); + } + + @Test + public void moduleSubcomponents_listsSubcomponentBuilder() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + moduleType.importStatement(), + "", + moduleType.annotationWithSubcomponent("Sub.Builder.class"), + "class TestModule {}"); + JavaFileObject subcomponent = + JavaFileObjects.forSourceLines( + "test.Sub", + "package test;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent", + "interface Sub {", + " @Subcomponent.Builder", + " interface Builder {", + " Sub build();", + " }", + "}"); + assertThat(module, subcomponent) + .processedWith(new ComponentProcessor()) + .failsToCompile() + .withErrorContaining( + "test.Sub.Builder is a @Subcomponent.Builder. Did you mean to use test.Sub?") + .in(module) + .onLine(5); + } + + @Test + public void moduleSubcomponents_listsProductionSubcomponentBuilder() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + moduleType.importStatement(), + "", + moduleType.annotationWithSubcomponent("Sub.Builder.class"), + "class TestModule {}"); + JavaFileObject subcomponent = + JavaFileObjects.forSourceLines( + "test.Sub", + "package test;", + "", + "import dagger.producers.ProductionSubcomponent;", + "", + "@ProductionSubcomponent", + "interface Sub {", + " @ProductionSubcomponent.Builder", + " interface Builder {", + " Sub build();", + " }", + "}"); + assertThat(module, subcomponent) + .processedWith(new ComponentProcessor()) + .failsToCompile() + .withErrorContaining( + "test.Sub.Builder is a @ProductionSubcomponent.Builder. Did you mean to use test.Sub?") + .in(module) + .onLine(5); + } + + @Test + public void moduleSubcomponents_noSubcomponentBuilder() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + moduleType.importStatement(), + "", + moduleType.annotationWithSubcomponent("NoBuilder.class"), + "class TestModule {}"); + JavaFileObject subcomponent = + JavaFileObjects.forSourceLines( + "test.NoBuilder", + "package test;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent", + "interface NoBuilder {}"); + assertThat(module, subcomponent) + .processedWith(new ComponentProcessor()) + .failsToCompile() + .withErrorContaining( + "test.NoBuilder doesn't have a @Subcomponent.Builder, which is required when used " + + "with @" + + moduleType.simpleName() + + ".subcomponents") + .in(module) + .onLine(5); + } + + @Test + public void moduleSubcomponents_noProductionSubcomponentBuilder() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + moduleType.importStatement(), + "", + moduleType.annotationWithSubcomponent("NoBuilder.class"), + "class TestModule {}"); + JavaFileObject subcomponent = + JavaFileObjects.forSourceLines( + "test.NoBuilder", + "package test;", + "", + "import dagger.producers.ProductionSubcomponent;", + "", + "@ProductionSubcomponent", + "interface NoBuilder {}"); + assertThat(module, subcomponent) + .processedWith(new ComponentProcessor()) + .failsToCompile() + .withErrorContaining( + "test.NoBuilder doesn't have a @ProductionSubcomponent.Builder, which is required " + + "when used with @" + + moduleType.simpleName() + + ".subcomponents") + .in(module) + .onLine(5); + } + + @Test + public void moduleSubcomponentsAreTypes() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.Module;", + "", + "@Module(subcomponents = int.class)", + "class TestModule {}"); + assertThat(module) + .processedWith(new ComponentProcessor()) + .failsToCompile() + .withErrorContaining("int is not a valid subcomponent type") + .in(module) + .onLine(5); + } +} diff --git a/compiler/src/test/java/dagger/internal/codegen/SubcomponentValidationTest.java b/compiler/src/test/java/dagger/internal/codegen/SubcomponentValidationTest.java index da8e013b89b..9c011f88267 100644 --- a/compiler/src/test/java/dagger/internal/codegen/SubcomponentValidationTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/SubcomponentValidationTest.java @@ -17,6 +17,8 @@ package dagger.internal.codegen; import static com.google.common.truth.Truth.assertAbout; +import static com.google.common.truth.Truth.assertThat; +import static com.google.testing.compile.JavaSourcesSubject.assertThat; import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; import static dagger.internal.codegen.GeneratedLines.GENERATED_ANNOTATION; @@ -876,12 +878,19 @@ public void subcomponentBuilderNamesShouldNotConflict() { "test.DaggerC", "package test;", "", + "import dagger.internal.Factory;", "import javax.annotation.Generated;", + "import javax.inject.Provider;", "", GENERATED_ANNOTATION, "public final class DaggerC implements C {", + "", + " private Provider fooBuilderProvider;", + " private Provider barBuilderProvider;", + "", " private DaggerC(Builder builder) {", " assert builder != null;", + " initialize(builder);", " }", "", " public static Builder builder() {", @@ -892,14 +901,33 @@ public void subcomponentBuilderNamesShouldNotConflict() { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize(final Builder builder) {", + " this.fooBuilderProvider = ", + " new Factory() {", + " @Override", + " public C.Foo.Sub.Builder get() {", + " return new Foo_SubBuilder();", + " }", + " };", + "", + " this.barBuilderProvider = ", + " new Factory() {", + " @Override", + " public C.Bar.Sub.Builder get() {", + " return new Bar_SubBuilder();", + " }", + " };", + " }", + "", " @Override", " public C.Foo.Sub.Builder fooBuilder() {", - " return new Foo_SubBuilder();", + " return fooBuilderProvider.get();", " }", "", " @Override", " public C.Bar.Sub.Builder barBuilder() {", - " return new Bar_SubBuilder();", + " return barBuilderProvider.get();", " }", "", " public static final class Builder {", @@ -944,4 +972,60 @@ public void subcomponentBuilderNamesShouldNotConflict() { .and() .generatesSources(componentGeneratedFile); } + + @Test + public void duplicateBindingWithSubcomponentDeclaration() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "", + "@Module(subcomponents = Sub.class)", + "class TestModule {", + " @Provides Sub.Builder providesConflictsWithModuleSubcomponents() { return null; }", + " @Provides Object usesSubcomponentBuilder(Sub.Builder builder) {", + " return builder.toString();", + " }", + "}"); + + JavaFileObject subcomponent = + JavaFileObjects.forSourceLines( + "test.Sub", + "package test;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent", + "interface Sub {", + " @Subcomponent.Builder", + " interface Builder {", + " Sub build();", + " }", + "}"); + + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.Sub", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface C {", + " Object dependsOnBuilder();", + "}"); + + assertThat(module, component, subcomponent) + .processedWith(new ComponentProcessor()) + .failsToCompile() + .withErrorContaining("test.Sub.Builder is bound multiple times:") + .and() + .withErrorContaining( + "@Provides test.Sub.Builder test.TestModule.providesConflictsWithModuleSubcomponents()") + .and() + .withErrorContaining("@Module(subcomponents = test.Sub.class) for test.TestModule"); + } } diff --git a/core/src/main/java/dagger/BindsOptionalOf.java b/core/src/main/java/dagger/BindsOptionalOf.java new file mode 100644 index 00000000000..481453615a7 --- /dev/null +++ b/core/src/main/java/dagger/BindsOptionalOf.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 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; + +import static java.lang.annotation.ElementType.METHOD; + +import dagger.internal.Beta; +import java.lang.annotation.Documented; +import java.lang.annotation.Target; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Qualifier; + +/** + * Annotates methods that declare bindings for {@code com.google.common.base.Optional} containers of + * values from bindings that may or may not be present in the component. + * + *

If a module contains a method declaration like this: + * + *

+ * {@literal @BindsOptionalOf} abstract Foo optionalFoo();
+ * + * then any binding in the component can depend on an {@code Optional} of {@code Foo}. If there is + * no binding for {@code Foo} in the component, the {@code Optional} will be absent. If there is a + * binding for {@code Foo} in the component, the {@code Optional} will be present, and its value + * will be the value given by the binding for {@code Foo}. + * + *

A {@code @BindsOptionalOf} method: + * + *

    + *
  • must be {@code abstract} + *
  • may have a {@linkplain Qualifier qualifier} annotation + *
  • must not return {@code void} + *
  • must not have parameters + *
  • must not throw exceptions + *
  • must not return an unqualified type with an {@link Inject @Inject}-annotated constructor, + * since such a type is always present + *
+ * + *

Other bindings may inject any of: + * + *

    + *
  • {@code Optional} + *
  • {@code Optional>} + *
  • {@code Optional>} + *
  • {@code Optional>>} + *
+ * + * or a {@link Provider}, {@link Lazy}, or {@link Provider} of {@link Lazy} of any of the above. + * + *

Explicit bindings for any of the above will conflict with a {@code @BindsOptionalOf} binding. + */ +@Documented +@Beta +@Target(METHOD) +public @interface BindsOptionalOf {} diff --git a/core/src/main/java/dagger/Module.java b/core/src/main/java/dagger/Module.java index 0ebf9b9415a..ad88f0e81da 100644 --- a/core/src/main/java/dagger/Module.java +++ b/core/src/main/java/dagger/Module.java @@ -16,6 +16,7 @@ package dagger; +import dagger.internal.Beta; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -34,4 +35,12 @@ * to the object graph. */ Class[] includes() default {}; + + /** + * Any {@link Subcomponent}- or {@link dagger.producers.ProductionSubcomponent}-annotated classes + * which should be children of the component in which this module is installed. A subcomponent may + * be listed in more than one module in a component. + */ + @Beta + Class[] subcomponents() default {}; } diff --git a/producers/src/main/java/dagger/producers/ProducerModule.java b/producers/src/main/java/dagger/producers/ProducerModule.java index ae23c2ef443..b55be724c54 100644 --- a/producers/src/main/java/dagger/producers/ProducerModule.java +++ b/producers/src/main/java/dagger/producers/ProducerModule.java @@ -41,4 +41,11 @@ * inclusions recursively, are all contributed to the object graph. */ Class[] includes() default {}; + + /** + * Any {@link dagger.Subcomponent}- or {@link ProductionSubcomponent}-annotated classes which + * should be children of the component in which this module is installed. A subcomponent may be + * listed in more than one module in a component. + */ + Class[] subcomponents() default {}; }