diff options
131 files changed, 5259 insertions, 1974 deletions
diff --git a/Android.mk b/Android.mk index f88f64120..08419f01d 100644 --- a/Android.mk +++ b/Android.mk @@ -24,7 +24,7 @@ LOCAL_MODULE_TAGS := optional LOCAL_MODULE_CLASS := JAVA_LIBRARIES LOCAL_SRC_FILES := $(call all-java-files-under, core/src/main/java/) -LOCAL_STATIC_JAVA_LIBRARIES := \ +LOCAL_JAVA_LIBRARIES := \ dagger2-inject-host \ guavalib @@ -41,12 +41,11 @@ LOCAL_MODULE_TAGS := optional LOCAL_MODULE_CLASS := JAVA_LIBRARIES LOCAL_SRC_FILES := $(call all-java-files-under, producers/src/main/java/) -LOCAL_STATIC_JAVA_LIBRARIES := \ +LOCAL_JAVA_LIBRARIES := \ dagger2-host \ dagger2-inject-host \ guavalib -#LOCAL_JACK_ENABLED := disabled include $(BUILD_HOST_JAVA_LIBRARY) # build dagger2 compiler host jar @@ -59,6 +58,10 @@ LOCAL_MODULE_TAGS := optional LOCAL_MODULE_CLASS := JAVA_LIBRARIES LOCAL_SRC_FILES := $(call all-java-files-under, compiler/src/main/java/) +# Manually include META-INF/services/javax.annotation.processing.Processor +# as the AutoService processor doesn't work properly. +LOCAL_JAVA_RESOURCE_DIRS := resources + LOCAL_STATIC_JAVA_LIBRARIES := \ dagger2-host \ dagger2-auto-common-host \ @@ -73,12 +76,12 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ # Disable the default discovery for annotation processors and explicitly specify # the path and classes needed. This is needed because otherwise it breaks a code # indexing tool that doesn't, as yet do automatic discovery. -PROCESSOR_JARS := \ - $(LOCAL_PATH)/../../out/host/common/obj/JAVA_LIBRARIES/guavalib_intermediates/javalib.jar \ - $(LOCAL_PATH)/lib/auto-common-1.0-20151022.071545-39$(COMMON_JAVA_PACKAGE_SUFFIX) \ - $(LOCAL_PATH)/lib/auto-factory-1.0-20150915.183854-35$(COMMON_JAVA_PACKAGE_SUFFIX) \ - $(LOCAL_PATH)/lib/auto-service-1.0-rc2$(COMMON_JAVA_PACKAGE_SUFFIX) \ - $(LOCAL_PATH)/lib/auto-value-1.0$(COMMON_JAVA_PACKAGE_SUFFIX) \ +PROCESSOR_LIBRARIES := \ + dagger2-auto-common-host \ + dagger2-auto-factory-host \ + dagger2-auto-service-host \ + dagger2-auto-value-host \ + guavalib PROCESSOR_CLASSES := \ com.google.auto.factory.processor.AutoFactoryProcessor \ @@ -86,14 +89,8 @@ PROCESSOR_CLASSES := \ com.google.auto.value.processor.AutoAnnotationProcessor \ com.google.auto.value.processor.AutoValueProcessor -LOCAL_JAVACFLAGS += -processorpath $(subst $(space),:,$(strip $(PROCESSOR_JARS))) +include $(LOCAL_PATH)/java_annotation_processors.mk -# Specify only one processor class per -processor option as -# the indexing tool does not parse the -processor value as a -# comma separated list. -LOCAL_JAVACFLAGS += $(foreach class,$(PROCESSOR_CLASSES),-processor $(class)) - -#LOCAL_JACK_ENABLED := disabled include $(BUILD_HOST_JAVA_LIBRARY) # Build host dependencies. @@ -106,6 +103,6 @@ LOCAL_PREBUILT_JAVA_LIBRARIES := \ dagger2-auto-service-host:lib/auto-service-1.0-rc2$(COMMON_JAVA_PACKAGE_SUFFIX) \ dagger2-auto-value-host:lib/auto-value-1.0$(COMMON_JAVA_PACKAGE_SUFFIX) \ dagger2-google-java-format:lib/google-java-format-0.1-20151017.042846-2$(COMMON_JAVA_PACKAGE_SUFFIX) \ - dagger2-inject-host:lib/javax-inject$(COMMON_JAVA_PACKAGE_SUFFIX) \ + dagger2-inject-host:lib/javax-inject$(COMMON_JAVA_PACKAGE_SUFFIX) include $(BUILD_HOST_PREBUILT) diff --git a/CHANGELOG.md b/CHANGELOG.md index 957840eca..3a38bfc00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,58 @@ Change Log ========== -Version 1.2.0 *(2013-12-13)* ----------------------------- +Dagger 2 (Components) +--------------------- + +### Version 2.0.2 *(2015-11-03)* + +A patch release, most crucially including: + + * A fix to the way processor validation of types is done that permits dagger to play + more nicely with other processors, avoiding over-validating aspects that it doesn't + need, which may yet not have been generated by other processors in a different round + of processing. + * Some improved error reporting for edge-cases + * Fix to prevent incompatible versions of Guava on the classpath from blowing up processing + * Support a more robust set of types for map keys in map bindings (primitive types, etc.) + +### Version 2.0.1 *(2015-05-28)* + +A maintenance release fixing immediate issues following the Dagger 2.0 release, including: + + * Speed up Graph Validation (reduce build times by 10s of seconds on sampled large projects) + * Generate correct code for @MapKey annotation types (beta) + * Fix to properly emit code for class literal values in @MapKey annotations. + * Fix for injecting component dependencies + * Fixes to generated code to account for differences in generics handling in ecg vs. javac. + * Subcomponents can now be abstract classes. + * Subcomponents now properly build the object graph in some cases involving explicit bindings + and (sub)components without scope. + * Improve runtime performance of SetFactory (set multibindings) + * Other smaller fixes, refactorings, etc. + +### Version 2.0.0 *(2015-04-21)* + +The initial release of the 2.0 code-line, supporting: + + * `@Component` interfaces representing a custom API to access a graph of objects + * JSR-330 injection automation using `@Inject` signals, `@Qualifiers` + * Simple bindings of implementations to interfaces, custom provision of objects, and set-bindings + * Compile-time validation of graph structure (cycles, missing bindings, duplicate bindings) + * Generation of + - backing implementations for components + - factories for `@Inject` constructors and modules + - members-injectors for `@Inject` methods and fields + * Beta support for + - Map bindings + - [Producers](http://google.github.io/dagger/api/latest/dagger/producers/Producer.html) + +============================================================== + +Dagger 1 (ObjectGraph) +---------------------- + +### Version 1.2.0 *(2013-12-13)* * Numerous performance improvements in both the compiler and runtime. * Use more efficient `String` concatenation. @@ -15,8 +65,7 @@ Version 1.2.0 *(2013-12-13)* module adapters. -Version 1.1.0 *(2013-08-05)* ----------------------------- +### Version 1.1.0 *(2013-08-05)* * Module loading now requires code generation via the 'dagger-compiler' artifact. * Allow multiple contributions to Set binding via `Provides.Type.SET_VALUES`. @@ -27,14 +76,12 @@ Version 1.1.0 *(2013-08-05)* * Update JavaWriter to 2.1.1. -Version 1.0.1 *(2013-06-03)* ----------------------------- +### Version 1.0.1 *(2013-06-03)* * Explicitly forbid declaring `@Inject` on a class type (e.g., `@Inject class Foo {}`). * Update JavaWriter to 1.0.5. -Version 1.0.0 *(2013-05-07)* ----------------------------- +### Version 1.0.0 *(2013-05-07)* Initial release. diff --git a/README.android b/README.android index 94ed4714c..1983221b5 100644 --- a/README.android +++ b/README.android @@ -2,7 +2,11 @@ URL: https://github.com/google/dagger.git License: Apache 2 Description: "Dagger 2 - A fast dependency injector for Android and Java" -Version: fdf1a9e905782dd2885799c6b34d9f1da842a7e1 +Version: 91f7d8bda5b1ef2fdf768758c88d3e5f069210ea + +Upstream depends (slightly) on Guava v19 but we only have Guava v18 in +Android at the moment so we have reverted those changes that introduced +a dependency on Guava v19. Local Patches: - None + Revert "Stop using deprecated Futures method." - upstream 0a2ca81c0f78c621968deb58f4b42117db43fec4 diff --git a/compiler/pom.xml b/compiler/pom.xml index 40e0fa27d..72252f1dd 100644 --- a/compiler/pom.xml +++ b/compiler/pom.xml @@ -46,25 +46,27 @@ <artifactId>auto-common</artifactId> </dependency> <dependency> - <groupId>com.google.auto.service</groupId> - <artifactId>auto-service</artifactId> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> <optional>true</optional> </dependency> - <!-- TODO(gak): Restore this presumably as javapoet when appropriate. <dependency> - <groupId>com.squareup</groupId> - <artifactId>javawriter</artifactId> + <groupId>com.google.googlejavaformat</groupId> + <artifactId>google-java-format</artifactId> </dependency> - --> <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> + <groupId>com.google.auto.service</groupId> + <artifactId>auto-service</artifactId> + <scope>provided</scope> <!-- to leave out of the all-deps jar --> </dependency> - <dependency> <groupId>com.google.auto.value</groupId> <artifactId>auto-value</artifactId> - <optional>true</optional> + <scope>provided</scope> <!-- to leave out of the all-deps jar --> <version>1.0</version> </dependency> @@ -187,6 +189,16 @@ <shadedPattern>dagger.shaded.auto.common</shadedPattern> </relocation> </relocations> + <filters> + <filter> + <artifact>*:*</artifact> + <excludes> + <exclude>META-INF/*.SF</exclude> + <exclude>META-INF/*.DSA</exclude> + <exclude>META-INF/*.RSA</exclude> + </excludes> + </filter> + </filters> </configuration> </execution> </executions> diff --git a/compiler/src/it/functional-tests/pom.xml b/compiler/src/it/functional-tests/pom.xml index 1eca20c14..ce3dd4e62 100644 --- a/compiler/src/it/functional-tests/pom.xml +++ b/compiler/src/it/functional-tests/pom.xml @@ -29,6 +29,10 @@ limitations under the License. <name>Functional Tests</name> <dependencies> <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + <dependency> <groupId>com.google.dagger</groupId> <artifactId>dagger</artifactId> <version>${project.version}</version> @@ -40,18 +44,22 @@ limitations under the License. <optional>true</optional> </dependency> <dependency> + <groupId>javax.inject</groupId> + <artifactId>javax.inject-tck</artifactId> + </dependency> + <dependency> <!-- For map-bindings --> <groupId>com.google.auto.value</groupId> <artifactId>auto-value</artifactId> <version>${auto.value.version}</version> - <optional>true</optional> + <scope>provided</scope> <!-- to leave out of the all-deps jar --> </dependency> <dependency> <!-- For map-bindings --> <groupId>com.google.auto.factory</groupId> <artifactId>auto-factory</artifactId> <version>${auto.factory.version}</version> - <optional>true</optional> + <scope>provided</scope> <!-- to leave out of the all-deps jar --> </dependency> <dependency> diff --git a/compiler/src/it/functional-tests/src/main/java/test/MultibindingComponent.java b/compiler/src/it/functional-tests/src/main/java/test/MultibindingComponent.java index 7cad3bd1a..ac0624f83 100644 --- a/compiler/src/it/functional-tests/src/main/java/test/MultibindingComponent.java +++ b/compiler/src/it/functional-tests/src/main/java/test/MultibindingComponent.java @@ -33,6 +33,7 @@ import test.sub.ContributionsModule; ) interface MultibindingComponent { Map<String, String> map(); + Map<String, String[]> mapOfArrays(); Map<String, Provider<String>> mapOfProviders(); Set<String> mapKeys(); Collection<String> mapValues(); diff --git a/compiler/src/it/functional-tests/src/main/java/test/MultibindingModule.java b/compiler/src/it/functional-tests/src/main/java/test/MultibindingModule.java index f356850b3..4a7577e76 100644 --- a/compiler/src/it/functional-tests/src/main/java/test/MultibindingModule.java +++ b/compiler/src/it/functional-tests/src/main/java/test/MultibindingModule.java @@ -46,6 +46,18 @@ class MultibindingModule { return "bar value"; } + @Provides(type = MAP) + @StringKey("foo") + static String[] provideFooArrayValue(double doubleDependency) { + return new String[] {"foo1", "foo2"}; + } + + @Provides(type = MAP) + @StringKey("bar") + static String[] provideBarArrayValue() { + return new String[] {"bar1", "bar2"}; + } + @Provides(type = SET) static int provideFiveToSet() { return 5; diff --git a/compiler/src/it/functional-tests/src/main/java/test/builder/MiddleChild.java b/compiler/src/it/functional-tests/src/main/java/test/builder/MiddleChild.java index 59c29ab34..690c91ad7 100644 --- a/compiler/src/it/functional-tests/src/main/java/test/builder/MiddleChild.java +++ b/compiler/src/it/functional-tests/src/main/java/test/builder/MiddleChild.java @@ -24,6 +24,8 @@ interface MiddleChild { Grandchild.Builder grandchildBuilder(); + RequiresSubcomponentBuilder<Grandchild.Builder> requiresGrandchildBuilder(); + @Subcomponent.Builder interface Builder { MiddleChild build(); diff --git a/compiler/src/it/functional-tests/src/main/java/test/builder/ParentComponent.java b/compiler/src/it/functional-tests/src/main/java/test/builder/ParentComponent.java index f901b8863..584eff6ef 100644 --- a/compiler/src/it/functional-tests/src/main/java/test/builder/ParentComponent.java +++ b/compiler/src/it/functional-tests/src/main/java/test/builder/ParentComponent.java @@ -26,4 +26,6 @@ interface ParentComponent { MiddleChild.Builder middleBuilder(); OtherMiddleChild.Builder otherBuilder(); + + RequiresSubcomponentBuilder<MiddleChild.Builder> requiresMiddleChildBuilder(); } diff --git a/compiler/src/it/functional-tests/src/main/java/test/builder/RequiresSubcomponentBuilder.java b/compiler/src/it/functional-tests/src/main/java/test/builder/RequiresSubcomponentBuilder.java new file mode 100644 index 000000000..ee9963227 --- /dev/null +++ b/compiler/src/it/functional-tests/src/main/java/test/builder/RequiresSubcomponentBuilder.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package test.builder; + +import javax.inject.Inject; +import javax.inject.Provider; + +class RequiresSubcomponentBuilder<B> { + private final Provider<B> subcomponentBuilderProvider; + private final B subcomponentBuilder; + + @Inject + RequiresSubcomponentBuilder(Provider<B> subcomponentBuilderProvider, B subcomponentBuilder) { + this.subcomponentBuilderProvider = subcomponentBuilderProvider; + this.subcomponentBuilder = subcomponentBuilder; + } + + Provider<B> subcomponentBuilderProvider() { + return subcomponentBuilderProvider; + } + + B subcomponentBuilder() { + return subcomponentBuilder; + } +} diff --git a/compiler/src/it/functional-tests/src/main/java/test/subcomponent/ChildAbstractClassComponent.java b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/ChildAbstractClassComponent.java index 6c061bc5c..2529433ae 100644 --- a/compiler/src/it/functional-tests/src/main/java/test/subcomponent/ChildAbstractClassComponent.java +++ b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/ChildAbstractClassComponent.java @@ -17,6 +17,6 @@ package test.subcomponent; import dagger.Subcomponent; -@Subcomponent(modules = ChildModule.class) +@Subcomponent(modules = {ChildModule.class, StaticChildModule.class}) abstract class ChildAbstractClassComponent implements ChildComponent { } diff --git a/compiler/src/it/functional-tests/src/main/java/test/subcomponent/ChildComponent.java b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/ChildComponent.java index 67d66cae9..d3c28f2b4 100644 --- a/compiler/src/it/functional-tests/src/main/java/test/subcomponent/ChildComponent.java +++ b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/ChildComponent.java @@ -19,7 +19,7 @@ import dagger.Subcomponent; import java.util.Set; import javax.inject.Provider; -@Subcomponent(modules = ChildModule.class) +@Subcomponent(modules = {ChildModule.class, StaticChildModule.class}) interface ChildComponent { Provider<UnscopedType> getUnscopedTypeProvider(); @@ -28,4 +28,6 @@ interface ChildComponent { Set<Object> objectSet(); GrandchildComponent newGrandchildComponent(); + + Object object(); } diff --git a/compiler/src/it/functional-tests/src/main/java/test/subcomponent/StaticChildModule.java b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/StaticChildModule.java new file mode 100644 index 000000000..f7fd49058 --- /dev/null +++ b/compiler/src/it/functional-tests/src/main/java/test/subcomponent/StaticChildModule.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package test.subcomponent; + +import dagger.Module; +import dagger.Provides; + +@Module +final class StaticChildModule { + private StaticChildModule() {} + + @Provides static Object provideStaticObject() { + return "static"; + } +} diff --git a/compiler/src/it/functional-tests/src/test/java/test/MultibindingTest.java b/compiler/src/it/functional-tests/src/test/java/test/MultibindingTest.java index 5e06f848f..1b7e30233 100644 --- a/compiler/src/it/functional-tests/src/test/java/test/MultibindingTest.java +++ b/compiler/src/it/functional-tests/src/test/java/test/MultibindingTest.java @@ -49,6 +49,15 @@ public class MultibindingTest { assertThat(map).containsEntry("bar", "bar value"); } + @Test public void mapOfArrays() { + Map<String, String[]> map = multibindingComponent.mapOfArrays(); + assertThat(map).hasSize(2); + assertThat(map).containsKey("foo"); + assertThat(map.get("foo")).asList().containsExactly("foo1", "foo2").inOrder(); + assertThat(map).containsKey("bar"); + assertThat(map.get("bar")).asList().containsExactly("bar1", "bar2").inOrder(); + } + @Test public void mapOfProviders() { Map<String, Provider<String>> mapOfProviders = multibindingComponent.mapOfProviders(); assertThat(mapOfProviders).hasSize(2); diff --git a/compiler/src/it/functional-tests/src/test/java/test/builder/BuilderTest.java b/compiler/src/it/functional-tests/src/test/java/test/builder/BuilderTest.java index ba590d2d9..46f5388ec 100644 --- a/compiler/src/it/functional-tests/src/test/java/test/builder/BuilderTest.java +++ b/compiler/src/it/functional-tests/src/test/java/test/builder/BuilderTest.java @@ -163,7 +163,7 @@ public class BuilderTest { assertThat(child2.l()).isEqualTo(6L); assertThat(child2.b()).isEqualTo((byte)70); } - + @Test public void grandchildren() { ParentComponent parent = DaggerParentComponent.create(); @@ -222,4 +222,45 @@ public class BuilderTest { assertThat(child.i()).isEqualTo(21); } + @Test + public void requireSubcomponentBuilderProviders() { + ParentComponent parent = DaggerParentComponent.create(); + MiddleChild middle = + parent + .requiresMiddleChildBuilder() + .subcomponentBuilderProvider() + .get() + .set(new StringModule("sam")) + .build(); + Grandchild grandchild = + middle + .requiresGrandchildBuilder() + .subcomponentBuilderProvider() + .get() + .set(new IntModuleIncludingDoubleAndFloat(12)) + .build(); + assertThat(middle.s()).isEqualTo("sam"); + assertThat(grandchild.i()).isEqualTo(12); + assertThat(grandchild.s()).isEqualTo("sam"); + } + + @Test + public void requireSubcomponentBuilders() { + ParentComponent parent = DaggerParentComponent.create(); + MiddleChild middle = + parent + .requiresMiddleChildBuilder() + .subcomponentBuilder() + .set(new StringModule("sam")) + .build(); + Grandchild grandchild = + middle + .requiresGrandchildBuilder() + .subcomponentBuilder() + .set(new IntModuleIncludingDoubleAndFloat(12)) + .build(); + assertThat(middle.s()).isEqualTo("sam"); + assertThat(grandchild.i()).isEqualTo(12); + assertThat(grandchild.s()).isEqualTo("sam"); + } } diff --git a/compiler/src/it/producers-functional-tests/pom.xml b/compiler/src/it/producers-functional-tests/pom.xml index bc1c3c7b5..a0d16498a 100644 --- a/compiler/src/it/producers-functional-tests/pom.xml +++ b/compiler/src/it/producers-functional-tests/pom.xml @@ -29,6 +29,10 @@ limitations under the License. <name>Producers Functional Tests</name> <dependencies> <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + <dependency> <groupId>com.google.dagger</groupId> <artifactId>dagger</artifactId> <version>${project.version}</version> @@ -56,10 +60,6 @@ limitations under the License. <scope>test</scope> </dependency> <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - </dependency> - <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/DependedComponent.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/DependedComponent.java index fa392dd8e..a80ea4968 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/DependedComponent.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/DependedComponent.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import com.google.common.util.concurrent.ListenableFuture; import dagger.Component; diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/DependedModule.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/DependedModule.java index 604107025..dc62612cd 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/DependedModule.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/DependedModule.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import dagger.Module; import dagger.Provides; diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/DependedProducerModule.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/DependedProducerModule.java index ad0c792a9..fe47c9973 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/DependedProducerModule.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/DependedProducerModule.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/DependedProductionComponent.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/DependedProductionComponent.java index 57f175812..ba98e3698 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/DependedProductionComponent.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/DependedProductionComponent.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import com.google.common.util.concurrent.ListenableFuture; import dagger.producers.ProductionComponent; diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/DependentComponent.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/DependentComponent.java index 4b14f99b5..85709f0f3 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/DependentComponent.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/DependentComponent.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import com.google.common.util.concurrent.ListenableFuture; import dagger.producers.ProductionComponent; diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/DependentProducerModule.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/DependentProducerModule.java index 234c088d3..e16c82212 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/DependentProducerModule.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/DependentProducerModule.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/MultibindingComponent.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/MultibindingComponent.java index 561ad4a89..6277e621a 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/MultibindingComponent.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/MultibindingComponent.java @@ -13,14 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import com.google.common.util.concurrent.ListenableFuture; +import dagger.producers.Produced; import dagger.producers.ProductionComponent; import java.util.Set; +import producerstest.MultibindingProducerModule.PossiblyThrowingSet; @ProductionComponent(modules = MultibindingProducerModule.class) interface MultibindingComponent { ListenableFuture<Set<String>> strs(); ListenableFuture<Integer> strCount(); + + ListenableFuture<Set<Produced<String>>> successfulSet(); + + @PossiblyThrowingSet + ListenableFuture<Set<Produced<String>>> possiblyThrowingSet(); } diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/MultibindingProducerModule.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/MultibindingProducerModule.java index 4651afcc6..09cd5bdbc 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/MultibindingProducerModule.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/MultibindingProducerModule.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Futures; @@ -21,29 +21,56 @@ import com.google.common.util.concurrent.ListenableFuture; import dagger.producers.ProducerModule; import dagger.producers.Produces; import java.util.Set; +import javax.inject.Qualifier; import static dagger.producers.Produces.Type.SET; import static dagger.producers.Produces.Type.SET_VALUES; @ProducerModule final class MultibindingProducerModule { - @Produces(type = SET) ListenableFuture<String> futureStr() { + @Qualifier + @interface PossiblyThrowingSet {} + + @Produces(type = SET) + static ListenableFuture<String> futureStr() { return Futures.immediateFuture("foo"); } - @Produces(type = SET) String str() { + @Produces(type = SET) + static String str() { return "bar"; } - @Produces(type = SET_VALUES) ListenableFuture<Set<String>> futureStrs() { + @Produces(type = SET_VALUES) + static ListenableFuture<Set<String>> futureStrs() { return Futures.<Set<String>>immediateFuture(ImmutableSet.of("foo1", "foo2")); } - @Produces(type = SET_VALUES) Set<String> strs() { + @Produces(type = SET_VALUES) + static Set<String> strs() { return ImmutableSet.of("bar1", "bar2"); } - @Produces int strCount(Set<String> strs) { + @Produces + static int strCount(Set<String> strs) { return strs.size(); } + + @Produces(type = SET) + @PossiblyThrowingSet + static String successfulStringForSet() { + return "singleton"; + } + + @Produces(type = SET_VALUES) + @PossiblyThrowingSet + static Set<String> successfulStringsForSet() { + return ImmutableSet.of("double", "ton"); + } + + @Produces(type = SET) + @PossiblyThrowingSet + static String throwingStringForSet() { + throw new RuntimeException("monkey"); + } } diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/Request.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/Request.java index 039d0fe55..0227be6bd 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/Request.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/Request.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import javax.inject.Inject; diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/Response.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/Response.java index 7a46e5b0e..8618ff5e1 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/Response.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/Response.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; final class Response { private final String data; diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/ResponseModule.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/ResponseModule.java index 4a83c6c83..1edbe8aa6 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/ResponseModule.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/ResponseModule.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import dagger.Module; import dagger.Provides; diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/ResponseProducerModule.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/ResponseProducerModule.java index f3ad22939..0ef2ae886 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/ResponseProducerModule.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/ResponseProducerModule.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/SimpleComponent.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/SimpleComponent.java index 583cd50f0..1d1e49233 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/SimpleComponent.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/SimpleComponent.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import com.google.common.util.concurrent.ListenableFuture; import dagger.producers.ProductionComponent; diff --git a/compiler/src/it/producers-functional-tests/src/main/java/test/SimpleProducerModule.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/SimpleProducerModule.java index 98f3cfb56..2d831ed7f 100644 --- a/compiler/src/it/producers-functional-tests/src/main/java/test/SimpleProducerModule.java +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/SimpleProducerModule.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Futures; diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/badexecutor/ComponentDependency.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/badexecutor/ComponentDependency.java new file mode 100644 index 000000000..7bba4ea4e --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/badexecutor/ComponentDependency.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.badexecutor; + +import com.google.common.util.concurrent.ListenableFuture; + +interface ComponentDependency { + ListenableFuture<Double> doubleDep(); +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/badexecutor/SimpleComponent.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/badexecutor/SimpleComponent.java new file mode 100644 index 000000000..6b3536eae --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/badexecutor/SimpleComponent.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.badexecutor; + +import com.google.common.util.concurrent.ListenableFuture; +import dagger.producers.ProductionComponent; + +/** + * A component that contains entry points that exercise different execution paths, for verifying the + * behavior when the executor throws a {@link java.util.concurrent.RejectedExecutionException}. + */ +@ProductionComponent(dependencies = ComponentDependency.class, modules = SimpleProducerModule.class) +interface SimpleComponent { + /** An entry point exposing a producer method with no args. */ + ListenableFuture<String> noArgStr(); + + /** An entry point exposing a producer method that depends on another producer method. */ + ListenableFuture<Integer> singleArgInt(); + + /** An entry point exposing a producer method that depends on a component dependency method. */ + ListenableFuture<Boolean> singleArgBool(); + + /** An entry point exposing a component dependency method. */ + ListenableFuture<Double> doubleDep(); +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/badexecutor/SimpleProducerModule.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/badexecutor/SimpleProducerModule.java new file mode 100644 index 000000000..00ab037bd --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/badexecutor/SimpleProducerModule.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.badexecutor; + +import dagger.producers.ProducerModule; +import dagger.producers.Produces; + +@ProducerModule +final class SimpleProducerModule { + @Produces + static String noArgStr() { + return "no arg string"; + } + + @Produces + static int singleArgInt(String arg) { + return arg.length(); + } + + @Produces + static boolean singleArgBool(double arg) { + return arg > 0.0; + } +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/builder/DepComponent.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/builder/DepComponent.java new file mode 100644 index 000000000..dadde7b05 --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/builder/DepComponent.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.builder; + +import com.google.common.util.concurrent.ListenableFuture; + +interface DepComponent { + ListenableFuture<Double> d(); +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/builder/IntModule.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/builder/IntModule.java new file mode 100644 index 000000000..7f99836d3 --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/builder/IntModule.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.builder; + +import dagger.Module; +import dagger.Provides; + +@Module +final class IntModule { + @Provides + static int i() { + return 42; + } +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/builder/StringModule.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/builder/StringModule.java new file mode 100644 index 000000000..cdf0793da --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/builder/StringModule.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.builder; + +import dagger.producers.ProducerModule; +import dagger.producers.Produces; + +@ProducerModule +final class StringModule { + @Produces + static String str(int i) { + return "arg: " + i; + } +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/builder/TestComponentWithBuilder.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/builder/TestComponentWithBuilder.java new file mode 100644 index 000000000..16dc9bad7 --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/builder/TestComponentWithBuilder.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.builder; + +import com.google.common.util.concurrent.ListenableFuture; +import dagger.producers.ProductionComponent; +import java.util.concurrent.Executor; + +@ProductionComponent( + modules = {StringModule.class, IntModule.class}, + dependencies = DepComponent.class +) +interface TestComponentWithBuilder { + ListenableFuture<String> s(); + ListenableFuture<Double> d(); + + @ProductionComponent.Builder + interface Builder { + Builder depComponent(DepComponent depComponent); + Builder strModule(StringModule strModule); + Builder executor(Executor executor); + TestComponentWithBuilder build(); + } +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/MonitoredComponent.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/MonitoredComponent.java new file mode 100644 index 000000000..48acbabac --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/MonitoredComponent.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.monitoring; + +import com.google.common.util.concurrent.ListenableFuture; +import dagger.producers.ProductionComponent; + +@ProductionComponent(modules = {MonitoringModule.class, StubModule.class, ServingModule.class}) +interface MonitoredComponent { + ListenableFuture<String> output(); +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/MonitoringModule.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/MonitoringModule.java new file mode 100644 index 000000000..0c4209076 --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/MonitoringModule.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.monitoring; + +import dagger.Module; +import dagger.Provides; +import dagger.producers.monitoring.ProductionComponentMonitor; + +import static dagger.Provides.Type.SET; + +@Module +final class MonitoringModule { + private final ProductionComponentMonitor.Factory monitorFactory; + + MonitoringModule(ProductionComponentMonitor.Factory monitorFactory) { + this.monitorFactory = monitorFactory; + } + + @Provides(type = SET) + ProductionComponentMonitor.Factory monitorFactory() { + return monitorFactory; + } +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/ServingModule.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/ServingModule.java new file mode 100644 index 000000000..d5bec22ce --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/ServingModule.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.monitoring; + +import com.google.common.util.concurrent.ListenableFuture; +import dagger.producers.ProducerModule; +import dagger.producers.Produces; +import javax.inject.Qualifier; +import producerstest.monitoring.StubModule.ForServer1; +import producerstest.monitoring.StubModule.ForServer2; + +@ProducerModule +final class ServingModule { + @Qualifier + @interface RequestData {} + + @Qualifier + @interface IntermediateData {} + + @Produces + @RequestData + static String requestData() { + return "Hello, World!"; + } + + @Produces + @IntermediateData + static ListenableFuture<String> callServer1( + @RequestData String data, @ForServer1 StringStub stub) { + return stub.run(data); + } + + @Produces + static ListenableFuture<String> callServer2( + @IntermediateData String data, @ForServer2 StringStub stub) { + return stub.run(data); + } +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/StringStub.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/StringStub.java new file mode 100644 index 000000000..195dd283d --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/StringStub.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.monitoring; + +import com.google.common.util.concurrent.ListenableFuture; + +interface StringStub { + ListenableFuture<String> run(String input); +} diff --git a/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/StubModule.java b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/StubModule.java new file mode 100644 index 000000000..dc8ab6260 --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/main/java/producerstest/monitoring/StubModule.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.monitoring; + +import dagger.Module; +import dagger.Provides; +import javax.inject.Qualifier; + +@Module +final class StubModule { + @Qualifier + @interface ForServer1 {} + + @Qualifier + @interface ForServer2 {} + + private final StringStub server1; + private final StringStub server2; + + StubModule(StringStub server1, StringStub server2) { + this.server1 = server1; + this.server2 = server2; + } + + @Provides + @ForServer1 + StringStub server1() { + return server1; + } + + @Provides + @ForServer2 + StringStub server2() { + return server2; + } +} diff --git a/compiler/src/it/producers-functional-tests/src/test/java/test/DependentTest.java b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/DependentTest.java index 15aa48165..b2533d735 100644 --- a/compiler/src/it/producers-functional-tests/src/test/java/test/DependentTest.java +++ b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/DependentTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; diff --git a/compiler/src/it/producers-functional-tests/src/test/java/producerstest/MultibindingTest.java b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/MultibindingTest.java new file mode 100644 index 000000000..4ddc7c6bc --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/MultibindingTest.java @@ -0,0 +1,73 @@ +/* +* Copyright (C) 2015 Google, Inc. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package producerstest; + +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.MoreExecutors; +import dagger.producers.Produced; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(JUnit4.class) +public class MultibindingTest { + @Test + public void setBinding() throws Exception { + MultibindingComponent multibindingComponent = + DaggerMultibindingComponent.builder().executor(MoreExecutors.directExecutor()).build(); + assertThat(multibindingComponent.strs().get()) + .containsExactly("foo", "foo1", "foo2", "bar", "bar1", "bar2"); + assertThat(multibindingComponent.strCount().get()).isEqualTo(6); + } + + @Test + public void setBindingOfProduced() throws Exception { + MultibindingComponent multibindingComponent = + DaggerMultibindingComponent.builder().executor(MoreExecutors.directExecutor()).build(); + assertThat(multibindingComponent.successfulSet().get()) + .containsExactly( + Produced.successful("foo"), + Produced.successful("foo1"), + Produced.successful("foo2"), + Produced.successful("bar"), + Produced.successful("bar1"), + Produced.successful("bar2")); + } + + @Test + public void setBindingOfProducedWithFailures() throws Exception { + MultibindingComponent multibindingComponent = + DaggerMultibindingComponent.builder().executor(MoreExecutors.directExecutor()).build(); + Set<Produced<String>> possiblyThrowingSet = multibindingComponent.possiblyThrowingSet().get(); + Set<String> successes = new HashSet<>(); + Set<ExecutionException> failures = new HashSet<>(); + for (Produced<String> str : possiblyThrowingSet) { + try { + successes.add(str.get()); + } catch (ExecutionException e) { + failures.add(e); + } + } + assertThat(successes).containsExactly("singleton", "double", "ton"); + assertThat(failures).hasSize(1); + assertThat(Iterables.getOnlyElement(failures).getCause()).hasMessage("monkey"); + } +} diff --git a/compiler/src/it/producers-functional-tests/src/test/java/test/ProducerFactoryTest.java b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/ProducerFactoryTest.java index 4c347318a..9a5602990 100644 --- a/compiler/src/it/producers-functional-tests/src/test/java/test/ProducerFactoryTest.java +++ b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/ProducerFactoryTest.java @@ -13,44 +13,50 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; +package producerstest; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; - import dagger.producers.Producer; import dagger.producers.monitoring.ProducerMonitor; import dagger.producers.monitoring.ProducerToken; import dagger.producers.monitoring.ProductionComponentMonitor; - +import java.util.concurrent.ExecutionException; +import javax.inject.Provider; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.concurrent.ExecutionException; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.when; @RunWith(JUnit4.class) public class ProducerFactoryTest { @Mock private ProductionComponentMonitor componentMonitor; - @Mock private ProducerMonitor monitor; + private ProducerMonitor monitor; + private Provider<ProductionComponentMonitor> componentMonitorProvider; @Before public void setUpMocks() { MockitoAnnotations.initMocks(this); + monitor = Mockito.mock(ProducerMonitor.class, Mockito.CALLS_REAL_METHODS); when(componentMonitor.producerMonitorFor(any(ProducerToken.class))).thenReturn(monitor); + componentMonitorProvider = + new Provider<ProductionComponentMonitor>() { + @Override + public ProductionComponentMonitor get() { + return componentMonitor; + } + }; } @Test @@ -58,7 +64,7 @@ public class ProducerFactoryTest { ProducerToken token = ProducerToken.create(SimpleProducerModule_StrFactory.class); Producer<String> producer = new SimpleProducerModule_StrFactory( - componentMonitor, MoreExecutors.directExecutor()); + MoreExecutors.directExecutor(), componentMonitorProvider); assertThat(producer.get().get()).isEqualTo("str"); InOrder order = inOrder(componentMonitor, monitor); order.verify(componentMonitor).producerMonitorFor(token); @@ -73,7 +79,7 @@ public class ProducerFactoryTest { Producer<Integer> intProducer = producerOfFuture(intFuture); Producer<String> producer = new SimpleProducerModule_StrWithArgFactory( - componentMonitor, MoreExecutors.directExecutor(), intProducer); + MoreExecutors.directExecutor(), componentMonitorProvider, intProducer); assertThat(producer.get().isDone()).isFalse(); intFuture.set(42); assertThat(producer.get().get()).isEqualTo("str with arg"); @@ -88,7 +94,7 @@ public class ProducerFactoryTest { Producer<SettableFuture<String>> strFutureProducer = producerOfFuture(strFutureFuture); Producer<String> producer = new SimpleProducerModule_SettableFutureStrFactory( - componentMonitor, MoreExecutors.directExecutor(), strFutureProducer); + MoreExecutors.directExecutor(), componentMonitorProvider, strFutureProducer); assertThat(producer.get().isDone()).isFalse(); InOrder order = inOrder(componentMonitor, monitor); @@ -115,7 +121,7 @@ public class ProducerFactoryTest { Producer<SettableFuture<String>> strFutureProducer = producerOfFuture(strFutureFuture); Producer<String> producer = new SimpleProducerModule_SettableFutureStrFactory( - componentMonitor, MoreExecutors.directExecutor(), strFutureProducer); + MoreExecutors.directExecutor(), componentMonitorProvider, strFutureProducer); assertThat(producer.get().isDone()).isFalse(); InOrder order = inOrder(componentMonitor, monitor); @@ -145,7 +151,7 @@ public class ProducerFactoryTest { Producer<String> producer = new SimpleProducerModule_ThrowingProducerFactory( - componentMonitor, MoreExecutors.directExecutor()); + MoreExecutors.directExecutor(), componentMonitorProvider); assertThat(producer.get().isDone()).isTrue(); InOrder order = inOrder(componentMonitor, monitor); @@ -164,25 +170,9 @@ public class ProducerFactoryTest { order.verifyNoMoreInteractions(); } - @Test - public void nullComponentMonitor() throws Exception { - Producer<String> producer = - new SimpleProducerModule_StrFactory(null, MoreExecutors.directExecutor()); - assertThat(producer.get().get()).isEqualTo("str"); - verifyZeroInteractions(componentMonitor, monitor); - } - - @Test - public void nullMonitor() throws Exception { - when(componentMonitor.producerMonitorFor(any(ProducerToken.class))).thenReturn(null); - - ProducerToken token = ProducerToken.create(SimpleProducerModule_StrFactory.class); - Producer<String> producer = - new SimpleProducerModule_StrFactory( - componentMonitor, MoreExecutors.directExecutor()); - assertThat(producer.get().get()).isEqualTo("str"); - verify(componentMonitor).producerMonitorFor(token); - verifyZeroInteractions(monitor); + @Test(expected = NullPointerException.class) + public void nullComponentMonitorProvider() throws Exception { + new SimpleProducerModule_StrFactory(MoreExecutors.directExecutor(), null); } private static <T> Producer<T> producerOfFuture(final ListenableFuture<T> future) { diff --git a/compiler/src/it/producers-functional-tests/src/test/java/test/SimpleTest.java b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/SimpleTest.java index e6e73961c..cacc0f11d 100644 --- a/compiler/src/it/producers-functional-tests/src/test/java/test/SimpleTest.java +++ b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/SimpleTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package test; +package producerstest; import com.google.common.util.concurrent.MoreExecutors; import org.junit.Test; diff --git a/compiler/src/it/producers-functional-tests/src/test/java/producerstest/badexecutor/BadExecutorTest.java b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/badexecutor/BadExecutorTest.java new file mode 100644 index 000000000..8a49797f5 --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/badexecutor/BadExecutorTest.java @@ -0,0 +1,74 @@ +package producerstest.badexecutor; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.RejectedExecutionException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +/** This test verifies behavior when the executor throws {@link RejectedExecutionException}. */ +@RunWith(JUnit4.class) +public final class BadExecutorTest { + private SimpleComponent component; + + @Before + public void setUpComponent() { + ComponentDependency dependency = + new ComponentDependency() { + @Override + public ListenableFuture<Double> doubleDep() { + return Futures.immediateFuture(42.0); + } + }; + ListeningExecutorService executorService = MoreExecutors.newDirectExecutorService(); + component = + DaggerSimpleComponent.builder() + .executor(executorService) + .componentDependency(dependency) + .build(); + executorService.shutdown(); + } + + @Test + public void rejectNoArgMethod() throws Exception { + try { + component.noArgStr().get(); + fail(); + } catch (ExecutionException e) { + assertThat(e.getCause()).isInstanceOf(RejectedExecutionException.class); + } + } + + @Test + public void rejectSingleArgMethod() throws Exception { + try { + component.singleArgInt().get(); + fail(); + } catch (ExecutionException e) { + assertThat(e.getCause()).isInstanceOf(RejectedExecutionException.class); + } + } + + @Test + public void rejectSingleArgFromComponentDepMethod() throws Exception { + try { + component.singleArgBool().get(); + fail(); + } catch (ExecutionException e) { + assertThat(e.getCause()).isInstanceOf(RejectedExecutionException.class); + } + } + + @Test + public void doNotRejectComponentDepMethod() throws Exception { + assertThat(component.doubleDep().get()).isEqualTo(42.0); + } +} diff --git a/compiler/src/it/producers-functional-tests/src/test/java/producerstest/builder/ProductionComponentBuilderTest.java b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/builder/ProductionComponentBuilderTest.java new file mode 100644 index 000000000..715761df4 --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/builder/ProductionComponentBuilderTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.builder; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static com.google.common.truth.Truth.assertThat; + +/** Tests for {@link dagger.producers.ProductionComponent.Builder}. */ +@RunWith(JUnit4.class) +public final class ProductionComponentBuilderTest { + + @Test + public void successfulBuild() throws Exception { + TestComponentWithBuilder component = + DaggerTestComponentWithBuilder.builder() + .executor(MoreExecutors.directExecutor()) + .depComponent(depComponent(15.3)) + .strModule(new StringModule()) + .build(); + assertThat(component.s().get()).isEqualTo("arg: 42"); + assertThat(component.d().get()).isEqualTo(15.3); + } + + @Test + public void successfulBuild_withMissingZeroArgModule() throws Exception { + TestComponentWithBuilder component = + DaggerTestComponentWithBuilder.builder() + .executor(MoreExecutors.directExecutor()) + .depComponent(depComponent(15.3)) + .build(); + assertThat(component.s().get()).isEqualTo("arg: 42"); + assertThat(component.d().get()).isEqualTo(15.3); + } + + @Test(expected = IllegalStateException.class) + public void missingExecutor() { + DaggerTestComponentWithBuilder.builder() + .depComponent(depComponent(15.3)) + .strModule(new StringModule()) + .build(); + } + + @Test(expected = IllegalStateException.class) + public void missingDepComponent() { + DaggerTestComponentWithBuilder.builder() + .executor(MoreExecutors.directExecutor()) + .strModule(new StringModule()) + .build(); + } + + private static DepComponent depComponent(final double value) { + return new DepComponent() { + @Override + public ListenableFuture<Double> d() { + return Futures.immediateFuture(value); + } + }; + } +} diff --git a/compiler/src/it/producers-functional-tests/src/test/java/producerstest/monitoring/MonitoringTest.java b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/monitoring/MonitoringTest.java new file mode 100644 index 000000000..3efb2b5b1 --- /dev/null +++ b/compiler/src/it/producers-functional-tests/src/test/java/producerstest/monitoring/MonitoringTest.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package producerstest.monitoring; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import dagger.producers.monitoring.ProducerMonitor; +import dagger.producers.monitoring.ProducerToken; +import dagger.producers.monitoring.ProductionComponentMonitor; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +/** Tests for production components using monitoring. */ +@RunWith(JUnit4.class) +public final class MonitoringTest { + @Mock private ProductionComponentMonitor.Factory componentMonitorFactory; + @Mock private StringStub server1; + @Mock private StringStub server2; + private SettableFuture<String> server1Future; + private SettableFuture<String> server2Future; + private FakeProductionComponentMonitor componentMonitor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + componentMonitor = new FakeProductionComponentMonitor(); + when(componentMonitorFactory.create(any())).thenReturn(componentMonitor); + server1Future = SettableFuture.create(); + server2Future = SettableFuture.create(); + when(server1.run(any(String.class))).thenReturn(server1Future); + when(server2.run(any(String.class))).thenReturn(server2Future); + } + + @Test + public void basicMonitoring() throws Exception { + MonitoredComponent component = + DaggerMonitoredComponent.builder() + .executor(MoreExecutors.directExecutor()) + .monitoringModule(new MonitoringModule(componentMonitorFactory)) + .stubModule(new StubModule(server1, server2)) + .build(); + ListenableFuture<String> output = component.output(); + assertThat(componentMonitor.monitors).hasSize(3); + ImmutableList<Map.Entry<ProducerToken, ProducerMonitor>> entries = + ImmutableList.copyOf(componentMonitor.monitors.entrySet()); + assertThat(entries.get(0).getKey().toString()).contains("CallServer2"); + assertThat(entries.get(1).getKey().toString()).contains("CallServer1"); + assertThat(entries.get(2).getKey().toString()).contains("RequestData"); + + ProducerMonitor callServer2Monitor = entries.get(0).getValue(); + ProducerMonitor callServer1Monitor = entries.get(1).getValue(); + ProducerMonitor requestDataMonitor = entries.get(2).getValue(); + + InOrder inOrder = inOrder(requestDataMonitor, callServer1Monitor, callServer2Monitor); + inOrder.verify(requestDataMonitor).methodStarting(); + inOrder.verify(requestDataMonitor).methodFinished(); + inOrder.verify(requestDataMonitor).succeeded("Hello, World!"); + inOrder.verify(callServer1Monitor).methodStarting(); + inOrder.verify(callServer1Monitor).methodFinished(); + verifyNoMoreInteractions(requestDataMonitor, callServer1Monitor, callServer2Monitor); + + server1Future.set("server 1 response"); + inOrder.verify(callServer1Monitor).succeeded("server 1 response"); + inOrder.verify(callServer2Monitor).methodStarting(); + inOrder.verify(callServer2Monitor).methodFinished(); + verifyNoMoreInteractions(requestDataMonitor, callServer1Monitor, callServer2Monitor); + + server2Future.set("server 2 response"); + inOrder.verify(callServer2Monitor).succeeded("server 2 response"); + verifyNoMoreInteractions(requestDataMonitor, callServer1Monitor, callServer2Monitor); + assertThat(output.get()).isEqualTo("server 2 response"); + } + + @Test + public void basicMonitoringWithFailure() throws Exception { + MonitoredComponent component = + DaggerMonitoredComponent.builder() + .executor(MoreExecutors.directExecutor()) + .monitoringModule(new MonitoringModule(componentMonitorFactory)) + .stubModule(new StubModule(server1, server2)) + .build(); + ListenableFuture<String> output = component.output(); + assertThat(componentMonitor.monitors).hasSize(3); + ImmutableList<Map.Entry<ProducerToken, ProducerMonitor>> entries = + ImmutableList.copyOf(componentMonitor.monitors.entrySet()); + assertThat(entries.get(0).getKey().toString()).contains("CallServer2"); + assertThat(entries.get(1).getKey().toString()).contains("CallServer1"); + assertThat(entries.get(2).getKey().toString()).contains("RequestData"); + + ProducerMonitor callServer2Monitor = entries.get(0).getValue(); + ProducerMonitor callServer1Monitor = entries.get(1).getValue(); + ProducerMonitor requestDataMonitor = entries.get(2).getValue(); + + InOrder inOrder = inOrder(requestDataMonitor, callServer1Monitor, callServer2Monitor); + inOrder.verify(requestDataMonitor).methodStarting(); + inOrder.verify(requestDataMonitor).methodFinished(); + inOrder.verify(requestDataMonitor).succeeded("Hello, World!"); + inOrder.verify(callServer1Monitor).methodStarting(); + inOrder.verify(callServer1Monitor).methodFinished(); + verifyNoMoreInteractions(requestDataMonitor, callServer1Monitor, callServer2Monitor); + + RuntimeException cause = new RuntimeException("monkey"); + server1Future.setException(cause); + inOrder.verify(callServer1Monitor).failed(cause); + inOrder.verify(callServer2Monitor).failed(any(Throwable.class)); + verifyNoMoreInteractions(requestDataMonitor, callServer1Monitor, callServer2Monitor); + try { + output.get(); + fail(); + } catch (ExecutionException e) { + assertThat(Throwables.getRootCause(e)).isSameAs(cause); + } + } + + private static final class FakeProductionComponentMonitor implements ProductionComponentMonitor { + final Map<ProducerToken, ProducerMonitor> monitors = new LinkedHashMap<>(); + + @Override + public ProducerMonitor producerMonitorFor(ProducerToken token) { + ProducerMonitor monitor = mock(ProducerMonitor.class); + monitors.put(token, monitor); + return monitor; + } + } +} diff --git a/compiler/src/it/producers-functional-tests/src/test/java/test/MultibindingTest.java b/compiler/src/it/producers-functional-tests/src/test/java/test/MultibindingTest.java deleted file mode 100644 index 20c86dc52..000000000 --- a/compiler/src/it/producers-functional-tests/src/test/java/test/MultibindingTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* -* Copyright (C) 2015 Google, Inc. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -package test; - -import com.google.common.util.concurrent.MoreExecutors; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import static com.google.common.truth.Truth.assertThat; - -@RunWith(JUnit4.class) -public class MultibindingTest { - @Test public void multibinding() throws Exception { - MultibindingComponent multibindingComponent = DaggerMultibindingComponent.builder() - .executor(MoreExecutors.directExecutor()) - .build(); - assertThat(multibindingComponent.strs().get()) - .containsExactly("foo", "foo1", "foo2", "bar", "bar1", "bar2"); - assertThat(multibindingComponent.strCount().get()).isEqualTo(6); - } -} diff --git a/compiler/src/main/java/dagger/internal/codegen/AbstractComponentWriter.java b/compiler/src/main/java/dagger/internal/codegen/AbstractComponentWriter.java index e0425c925..db890b246 100644 --- a/compiler/src/main/java/dagger/internal/codegen/AbstractComponentWriter.java +++ b/compiler/src/main/java/dagger/internal/codegen/AbstractComponentWriter.java @@ -19,7 +19,6 @@ import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.common.base.Joiner; import com.google.common.base.Optional; -import com.google.common.base.Predicates; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -41,7 +40,6 @@ import dagger.internal.SetFactory; import dagger.internal.codegen.ComponentDescriptor.BuilderSpec; import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor; import dagger.internal.codegen.ComponentGenerator.MemberSelect; -import dagger.internal.codegen.ComponentGenerator.ProxyClassAndField; import dagger.internal.codegen.writer.ClassName; import dagger.internal.codegen.writer.ClassWriter; import dagger.internal.codegen.writer.ConstructorWriter; @@ -53,25 +51,22 @@ import dagger.internal.codegen.writer.Snippet; import dagger.internal.codegen.writer.StringLiteral; import dagger.internal.codegen.writer.TypeName; import dagger.internal.codegen.writer.TypeNames; -import dagger.internal.codegen.writer.TypeWriter; import dagger.internal.codegen.writer.VoidName; import dagger.producers.Producer; import dagger.producers.internal.Producers; +import dagger.producers.internal.SetOfProducedProducer; import dagger.producers.internal.SetProducer; import java.util.Collection; -import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import javax.annotation.Generated; import javax.inject.Provider; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; @@ -88,6 +83,7 @@ import static com.google.auto.common.MoreTypes.asDeclared; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.any; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.AbstractComponentWriter.InitializationState.DELEGATED; import static dagger.internal.codegen.AbstractComponentWriter.InitializationState.INITIALIZED; @@ -95,20 +91,21 @@ import static dagger.internal.codegen.AbstractComponentWriter.InitializationStat import static dagger.internal.codegen.Binding.bindingPackageFor; import static dagger.internal.codegen.ComponentGenerator.MemberSelect.staticMethodInvocationWithCast; import static dagger.internal.codegen.ComponentGenerator.MemberSelect.staticSelect; +import static dagger.internal.codegen.ContributionBinding.contributionTypeFor; +import static dagger.internal.codegen.ContributionBinding.FactoryCreationStrategy.ENUM_INSTANCE; +import static dagger.internal.codegen.ContributionBinding.Kind.PROVISION; import static dagger.internal.codegen.ErrorMessages.CANNOT_RETURN_NULL_FROM_NON_NULLABLE_COMPONENT_METHOD; import static dagger.internal.codegen.MapKeys.getMapKeySnippet; import static dagger.internal.codegen.MembersInjectionBinding.Strategy.NO_OP; -import static dagger.internal.codegen.ProvisionBinding.FactoryCreationStrategy.ENUM_INSTANCE; -import static dagger.internal.codegen.ProvisionBinding.Kind.PROVISION; -import static dagger.internal.codegen.SourceFiles.factoryNameForProductionBinding; -import static dagger.internal.codegen.SourceFiles.factoryNameForProvisionBinding; import static dagger.internal.codegen.SourceFiles.frameworkTypeUsageStatement; +import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding; import static dagger.internal.codegen.SourceFiles.indexDependenciesByUnresolvedKey; import static dagger.internal.codegen.SourceFiles.membersInjectorNameForType; import static dagger.internal.codegen.Util.componentCanMakeNewInstances; import static dagger.internal.codegen.Util.getKeyTypeOfMap; import static dagger.internal.codegen.Util.getProvidedValueTypeOfMap; import static dagger.internal.codegen.Util.isMapWithNonProvidedValues; +import static dagger.internal.codegen.writer.Snippet.makeParametersSnippet; import static dagger.internal.codegen.writer.Snippet.memberSelectSnippet; import static dagger.internal.codegen.writer.Snippet.nullCheck; import static javax.lang.model.element.Modifier.ABSTRACT; @@ -131,12 +128,13 @@ abstract class AbstractComponentWriter { protected final Set<JavaWriter> javaWriters = new LinkedHashSet<>(); protected final ClassName name; protected final BindingGraph graph; - private final Map<String, ProxyClassAndField> packageProxies = new HashMap<>(); private final Map<BindingKey, InitializationState> initializationStates = new HashMap<>(); + private final Map<Binding, InitializationState> contributionInitializationStates = + new HashMap<>(); protected ClassWriter componentWriter; - private ImmutableMap<BindingKey, MemberSelect> memberSelectSnippets; - private ImmutableMap<ContributionBinding, MemberSelect> multibindingContributionSnippets; - private ImmutableSet<BindingKey> enumBindingKeys; + private final Map<BindingKey, MemberSelect> memberSelectSnippets = new HashMap<>(); + private final Map<ContributionBinding, MemberSelect> multibindingContributionSnippets = + new HashMap<>(); protected ConstructorWriter constructorWriter; protected Optional<ClassName> builderName = Optional.absent(); @@ -242,6 +240,16 @@ abstract class AbstractComponentWriter { initializationStates.put(bindingKey, state); } + private InitializationState getContributionInitializationState(Binding binding) { + return contributionInitializationStates.containsKey(binding) + ? contributionInitializationStates.get(binding) + : UNINITIALIZED; + } + + private void setContributionInitializationState(Binding binding, InitializationState state) { + contributionInitializationStates.put(binding, state); + } + ImmutableSet<JavaWriter> write() { if (javaWriters.isEmpty()) { writeComponent(); @@ -445,109 +453,51 @@ abstract class AbstractComponentWriter { protected abstract void addFactoryMethods(); private void addFields() { - Map<BindingKey, MemberSelect> memberSelectSnippetsBuilder = Maps.newHashMap(); - Map<ContributionBinding, MemberSelect> multibindingContributionSnippetsBuilder = - Maps.newHashMap(); - ImmutableSet.Builder<BindingKey> enumBindingKeysBuilder = ImmutableSet.builder(); - for (ResolvedBindings resolvedBindings : graph.resolvedBindings().values()) { - addField( - memberSelectSnippetsBuilder, - multibindingContributionSnippetsBuilder, - enumBindingKeysBuilder, - resolvedBindings); + addField(resolvedBindings); } - - memberSelectSnippets = ImmutableMap.copyOf(memberSelectSnippetsBuilder); - multibindingContributionSnippets = ImmutableMap.copyOf(multibindingContributionSnippetsBuilder); - enumBindingKeys = enumBindingKeysBuilder.build(); } - private void addField( - Map<BindingKey, MemberSelect> memberSelectSnippetsBuilder, - Map<ContributionBinding, MemberSelect> multibindingContributionSnippetsBuilder, - ImmutableSet.Builder<BindingKey> enumBindingKeysBuilder, - ResolvedBindings resolvedBindings) { + private void addField(ResolvedBindings resolvedBindings) { BindingKey bindingKey = resolvedBindings.bindingKey(); - // No field needed for unique contributions inherited from the parent. - if (resolvedBindings.isUniqueContribution() && resolvedBindings.ownedBindings().isEmpty()) { + // No field needed if there are no owned bindings. + if (resolvedBindings.ownedBindings().isEmpty()) { return; } - + // No field needed for bindings with no dependencies or state. Optional<MemberSelect> staticMemberSelect = staticMemberSelect(resolvedBindings); if (staticMemberSelect.isPresent()) { - // TODO(gak): refactor to use enumBindingKeys throughout the generator - enumBindingKeysBuilder.add(bindingKey); - memberSelectSnippetsBuilder.put(bindingKey, staticMemberSelect.get()); + memberSelectSnippets.put(bindingKey, staticMemberSelect.get()); return; } - String bindingPackage = bindingPackageFor(resolvedBindings.bindings()).or(name.packageName()); - - final Optional<String> proxySelector; - final TypeWriter classWithFields; - final Set<Modifier> fieldModifiers; - - if (bindingPackage.equals(name.packageName())) { - // no proxy - proxySelector = Optional.absent(); - // component gets the fields - classWithFields = componentWriter; - // private fields - fieldModifiers = EnumSet.of(PRIVATE); - } else { - // get or create the proxy - ProxyClassAndField proxyClassAndField = packageProxies.get(bindingPackage); - if (proxyClassAndField == null) { - JavaWriter proxyJavaWriter = JavaWriter.inPackage(bindingPackage); - javaWriters.add(proxyJavaWriter); - ClassWriter proxyWriter = proxyJavaWriter.addClass(name.simpleName() + "_PackageProxy"); - proxyWriter.annotate(Generated.class).setValue(ComponentProcessor.class.getCanonicalName()); - proxyWriter.addModifiers(PUBLIC, FINAL); - // create the field for the proxy in the component - FieldWriter proxyFieldWriter = - componentWriter.addField( - proxyWriter.name(), bindingPackage.replace('.', '_') + "_Proxy"); - proxyFieldWriter.addModifiers(PRIVATE, FINAL); - proxyFieldWriter.setInitializer("new %s()", proxyWriter.name()); - proxyClassAndField = ProxyClassAndField.create(proxyWriter, proxyFieldWriter); - packageProxies.put(bindingPackage, proxyClassAndField); - } - // add the field for the member select - proxySelector = Optional.of(proxyClassAndField.proxyFieldWriter().name()); - // proxy gets the fields - classWithFields = proxyClassAndField.proxyWriter(); - // public fields in the proxy - fieldModifiers = EnumSet.of(PUBLIC); - } - + Optional<String> bindingPackage = bindingPackageFor(resolvedBindings.bindings()); + boolean useRawType = bindingPackage.isPresent() + && !bindingPackage.get().equals(name.packageName()); if (bindingKey.kind().equals(BindingKey.Kind.CONTRIBUTION)) { - ImmutableSet<? extends ContributionBinding> contributionBindings = + ImmutableSet<ContributionBinding> contributionBindings = resolvedBindings.contributionBindings(); - if (ContributionBinding.bindingTypeFor(contributionBindings).isMultibinding()) { + if (ContributionBinding.contributionTypeFor(contributionBindings).isMultibinding()) { // note that here we rely on the order of the resolved bindings being from parent to child // otherwise, the numbering wouldn't work int contributionNumber = 0; for (ContributionBinding contributionBinding : contributionBindings) { if (!contributionBinding.isSyntheticBinding()) { contributionNumber++; - if (resolvedBindings.ownedBindings().contains(contributionBinding)) { + if (resolvedBindings.ownedContributionBindings().contains(contributionBinding)) { FrameworkField contributionBindingField = FrameworkField.createForSyntheticContributionBinding( - bindingKey, contributionNumber, contributionBinding); + contributionNumber, contributionBinding); FieldWriter contributionField = - classWithFields.addField( - contributionBindingField.frameworkType(), contributionBindingField.name()); - contributionField.addModifiers(fieldModifiers); + addFrameworkField(useRawType, contributionBindingField); ImmutableList<String> contributionSelectTokens = new ImmutableList.Builder<String>() - .addAll(proxySelector.asSet()) .add(contributionField.name()) .build(); - multibindingContributionSnippetsBuilder.put( + multibindingContributionSnippets.put( contributionBinding, MemberSelect.instanceSelect(name, memberSelectSnippet(contributionSelectTokens))); } @@ -557,51 +507,63 @@ abstract class AbstractComponentWriter { } FrameworkField bindingField = FrameworkField.createForResolvedBindings(resolvedBindings); - FieldWriter frameworkField = - classWithFields.addField(bindingField.frameworkType(), bindingField.name()); - frameworkField.addModifiers(fieldModifiers); + FieldWriter frameworkField = addFrameworkField(useRawType, bindingField); ImmutableList<String> memberSelectTokens = new ImmutableList.Builder<String>() - .addAll(proxySelector.asSet()) .add(frameworkField.name()) .build(); - memberSelectSnippetsBuilder.put( + memberSelectSnippets.put( bindingKey, MemberSelect.instanceSelect(name, Snippet.memberSelectSnippet(memberSelectTokens))); } + private FieldWriter addFrameworkField(boolean useRawType, + FrameworkField contributionBindingField) { + FieldWriter contributionField = + componentWriter.addField( + useRawType + ? contributionBindingField.frameworkType().type() + : contributionBindingField.frameworkType(), + contributionBindingField.name()); + contributionField.addModifiers(PRIVATE); + if (useRawType) { + contributionField.annotate(SuppressWarnings.class).setValue("rawtypes"); + } + return contributionField; + } + /** * If {@code resolvedBindings} is an unscoped provision binding with no factory arguments or a - * no-op members injection binding, then we do't need a field to hold its factory. In that case, + * no-op members injection binding, then we don't need a field to hold its factory. In that case, * this method returns the static member select snippet that returns the factory or no-op members * injector. */ private Optional<MemberSelect> staticMemberSelect(ResolvedBindings resolvedBindings) { - if (resolvedBindings.bindings().size() != 1) { - return Optional.absent(); - } switch (resolvedBindings.bindingKey().kind()) { case CONTRIBUTION: + if (resolvedBindings.contributionBindings().size() != 1) { + return Optional.absent(); + } ContributionBinding contributionBinding = getOnlyElement(resolvedBindings.contributionBindings()); - if (contributionBinding.bindingType().isMultibinding() - || !(contributionBinding instanceof ProvisionBinding)) { + if (contributionBinding.contributionType().isMultibinding() + || !(contributionBinding.bindingType().equals(Binding.Type.PROVISION))) { return Optional.absent(); } - ProvisionBinding provisionBinding = (ProvisionBinding) contributionBinding; - if (provisionBinding.factoryCreationStrategy().equals(ENUM_INSTANCE) - && !provisionBinding.scope().isPresent()) { + if (contributionBinding.factoryCreationStrategy().equals(ENUM_INSTANCE) + && !contributionBinding.scope().isPresent()) { return Optional.of( staticSelect( - factoryNameForProvisionBinding(provisionBinding), Snippet.format("create()"))); + generatedClassNameForBinding(contributionBinding), Snippet.format("create()"))); } break; case MEMBERS_INJECTION: - if (getOnlyElement(resolvedBindings.membersInjectionBindings()) - .injectionStrategy() - .equals(NO_OP)) { + Optional<MembersInjectionBinding> membersInjectionBinding = + resolvedBindings.membersInjectionBinding(); + if (membersInjectionBinding.isPresent() + && membersInjectionBinding.get().injectionStrategy().equals(NO_OP)) { return Optional.of( staticMethodInvocationWithCast( ClassName.fromClass(MembersInjectors.class), @@ -639,13 +601,14 @@ abstract class AbstractComponentWriter { interfaceMethod.annotate(Override.class); interfaceMethod.addModifiers(PUBLIC); BindingKey bindingKey = interfaceRequest.bindingKey(); + MemberSelect memberSelect = getMemberSelect(bindingKey); + Snippet memberSelectSnippet = memberSelect.getSnippetFor(name); switch (interfaceRequest.kind()) { case MEMBERS_INJECTOR: - Snippet membersInjectorSelect = getMemberSelectSnippet(bindingKey); List<? extends VariableElement> parameters = requestElement.getParameters(); if (parameters.isEmpty()) { // we're returning the framework type - interfaceMethod.body().addSnippet("return %s;", membersInjectorSelect); + interfaceMethod.body().addSnippet("return %s;", memberSelectSnippet); } else { VariableElement parameter = Iterables.getOnlyElement(parameters); Name parameterName = parameter.getSimpleName(); @@ -657,9 +620,7 @@ abstract class AbstractComponentWriter { .body() .addSnippet( "%s.injectMembers(%s);", - // In this case we know we won't need the cast because we're never going to - // pass the reference to anything. - membersInjectorSelect, + memberSelectSnippet, parameterName); if (!requestType.getReturnType().getKind().equals(VOID)) { interfaceMethod.body().addSnippet("return %s;", parameterName); @@ -667,7 +628,7 @@ abstract class AbstractComponentWriter { } break; case INSTANCE: - if (enumBindingKeys.contains(bindingKey) + if (memberSelect.staticMember() && bindingKey.key().type().getKind().equals(DECLARED) && !((DeclaredType) bindingKey.key().type()).getTypeArguments().isEmpty()) { // If using a parameterized enum type, then we need to store the factory @@ -679,7 +640,7 @@ abstract class AbstractComponentWriter { interfaceMethod .body() .addSnippet( - "%s factory = %s;", factoryType, getMemberSelectSnippet(bindingKey)); + "%s factory = %s;", factoryType, memberSelectSnippet); interfaceMethod.body().addSnippet("return factory.get();"); break; } @@ -694,7 +655,7 @@ abstract class AbstractComponentWriter { .addSnippet( "return %s;", frameworkTypeUsageStatement( - getMemberSelectSnippet(bindingKey), interfaceRequest.kind())); + memberSelectSnippet, interfaceRequest.kind())); break; default: throw new AssertionError(); @@ -712,13 +673,27 @@ abstract class AbstractComponentWriter { } } + private static final int SNIPPETS_PER_INITIALIZATION_METHOD = 100; + private void initializeFrameworkTypes() { - List<List<BindingKey>> partitions = - Lists.partition(graph.resolvedBindings().keySet().asList(), 100); + ImmutableList.Builder<Snippet> snippetsBuilder = ImmutableList.builder(); + for (BindingKey bindingKey : graph.resolvedBindings().keySet()) { + snippetsBuilder.add(initializeFrameworkType(bindingKey)); + } + ImmutableList<Snippet> snippets = snippetsBuilder.build(); + + List<List<Snippet>> partitions = Lists.partition(snippets, SNIPPETS_PER_INITIALIZATION_METHOD); for (int i = 0; i < partitions.size(); i++) { MethodWriter initializeMethod = componentWriter.addMethod(VoidName.VOID, "initialize" + ((i == 0) ? "" : i)); - initializeMethod.body(); + /* TODO(gak): Strictly speaking, we only need the suppression here if we are also initializing + * a raw field in this method, but the structure of this code makes it awkward to pass that + * bit through. This will be cleaned up when we no longer separate fields and initilization + * as we do now. */ + initializeMethod.annotate(SuppressWarnings.class).setValue("unchecked"); + for (Snippet snippet : partitions.get(i)) { + initializeMethod.body().addSnippet(snippet); + } initializeMethod.addModifiers(PRIVATE); if (builderName.isPresent()) { initializeMethod.addParameter(builderName.get(), "builder").addModifiers(FINAL); @@ -726,111 +701,141 @@ abstract class AbstractComponentWriter { } else { constructorWriter.body().addSnippet("%s();", initializeMethod.name()); } - for (BindingKey bindingKey : partitions.get(i)) { - ResolvedBindings resolvedBindings = graph.resolvedBindings().get(bindingKey); - switch (bindingKey.kind()) { - case CONTRIBUTION: - ImmutableSet<? extends ContributionBinding> bindings = - resolvedBindings.contributionBindings(); - - switch (ContributionBinding.bindingTypeFor(bindings)) { - case SET: - boolean hasOnlyProvisions = - Iterables.all(bindings, Predicates.instanceOf(ProvisionBinding.class)); - ImmutableList.Builder<Snippet> parameterSnippets = ImmutableList.builder(); - for (ContributionBinding binding : bindings) { - Optional<MemberSelect> multibindingContributionSnippet = - getMultibindingContributionSnippet(binding); - checkState( - multibindingContributionSnippet.isPresent(), "%s was not found", binding); - Snippet snippet = multibindingContributionSnippet.get().getSnippetFor(name); - if (multibindingContributionSnippet.get().owningClass().equals(name)) { - Snippet initializeSnippet = initializeFactoryForContributionBinding(binding); - initializeMethod.body().addSnippet("this.%s = %s;", snippet, initializeSnippet); - } - parameterSnippets.add(snippet); - } - Snippet initializeSetSnippet = - Snippet.format( - "%s.create(%s)", - hasOnlyProvisions - ? ClassName.fromClass(SetFactory.class) - : ClassName.fromClass(SetProducer.class), - Snippet.makeParametersSnippet(parameterSnippets.build())); - initializeMember(initializeMethod, bindingKey, initializeSetSnippet); - break; - case MAP: - if (Sets.filter(bindings, Predicates.instanceOf(ProductionBinding.class)) - .isEmpty()) { - @SuppressWarnings("unchecked") // checked by the instanceof filter above - ImmutableSet<ProvisionBinding> provisionBindings = - (ImmutableSet<ProvisionBinding>) bindings; - for (ProvisionBinding provisionBinding : provisionBindings) { - Optional<MemberSelect> multibindingContributionSnippet = - getMultibindingContributionSnippet(provisionBinding); - if (!isMapWithNonProvidedValues(provisionBinding.key().type()) - && multibindingContributionSnippet.isPresent() - && multibindingContributionSnippet.get().owningClass().equals(name)) { - initializeMethod - .body() - .addSnippet( - "this.%s = %s;", - multibindingContributionSnippet.get().getSnippetFor(name), - initializeFactoryForProvisionBinding(provisionBinding)); - } - } - initializeMember( - initializeMethod, bindingKey, initializeMapBinding(provisionBindings)); - } else { - // TODO(beder): Implement producer map bindings. - throw new IllegalStateException("producer map bindings not implemented yet"); - } - break; - case UNIQUE: - if (!resolvedBindings.ownedContributionBindings().isEmpty()) { - ContributionBinding binding = Iterables.getOnlyElement(bindings); - if (binding instanceof ProvisionBinding) { - ProvisionBinding provisionBinding = (ProvisionBinding) binding; - if (!provisionBinding.factoryCreationStrategy().equals(ENUM_INSTANCE) - || provisionBinding.scope().isPresent()) { - initializeDelegateFactories(binding, initializeMethod); - initializeMember( - initializeMethod, - bindingKey, - initializeFactoryForProvisionBinding(provisionBinding)); - } - } else if (binding instanceof ProductionBinding) { - ProductionBinding productionBinding = (ProductionBinding) binding; - initializeMember( - initializeMethod, - bindingKey, - initializeFactoryForProductionBinding(productionBinding)); - } else { - throw new AssertionError(); - } - } - break; - default: - throw new IllegalStateException(); - } - break; - case MEMBERS_INJECTION: - MembersInjectionBinding binding = - Iterables.getOnlyElement(resolvedBindings.membersInjectionBindings()); - if (!binding.injectionStrategy().equals(MembersInjectionBinding.Strategy.NO_OP)) { - initializeDelegateFactories(binding, initializeMethod); - initializeMember( - initializeMethod, bindingKey, initializeMembersInjectorForBinding(binding)); - } - break; + } + } + + /** + * Returns a single snippet representing the initialization of the framework type. + * + * <p>Note that this must be a single snippet because initialization snippets can be invoked from + * any place in any order. By requiring a single snippet (often of concatenated snippets) we + * ensure that things like local variables always behave as expected by the initialization logic. + */ + private Snippet initializeFrameworkType(BindingKey bindingKey) { + ResolvedBindings resolvedBindings = graph.resolvedBindings().get(bindingKey); + + // There's no field for inherited bindings. + if (resolvedBindings.ownedBindings().isEmpty()) { + return Snippet.format(""); + } + + switch (bindingKey.kind()) { + case CONTRIBUTION: + switch (contributionTypeFor(resolvedBindings.contributionBindings())) { + case SET: + return initializeSetMultibindings(resolvedBindings); + case MAP: + return initializeMapMultibindings(resolvedBindings); + case UNIQUE: + return initializeUniqueContributionBinding(resolvedBindings); default: throw new AssertionError(); } + + case MEMBERS_INJECTION: + return initializeMembersInjectionBinding(resolvedBindings); + + default: + throw new AssertionError(); + } + } + + private Snippet initializeSetMultibindings(ResolvedBindings resolvedBindings) { + ImmutableList.Builder<Snippet> initializationSnippets = ImmutableList.builder(); + + ImmutableList.Builder<Snippet> parameterSnippets = ImmutableList.builder(); + for (ContributionBinding binding : resolvedBindings.contributionBindings()) { + Optional<MemberSelect> multibindingContributionSnippet = + getMultibindingContributionSnippet(binding); + checkState(multibindingContributionSnippet.isPresent(), "%s was not found", binding); + Snippet snippet = multibindingContributionSnippet.get().getSnippetFor(name); + if (multibindingContributionSnippet.get().owningClass().equals(name) + // the binding might already be initialized by a different set binding that shares the + // same contributions (e.g., Set<T> and Set<Produced<T>>) + && getContributionInitializationState(binding) + .equals(InitializationState.UNINITIALIZED)) { + Snippet initializeSnippet = initializeFactoryForContributionBinding(binding); + initializationSnippets.add(Snippet.format("this.%s = %s;", snippet, initializeSnippet)); + setContributionInitializationState(binding, InitializationState.INITIALIZED); } + parameterSnippets.add(snippet); + } + Class<?> factoryClass = + Iterables.all(resolvedBindings.contributionBindings(), Binding.Type.PROVISION) + ? SetFactory.class + : Util.isSetOfProduced(resolvedBindings.bindingKey().key().type()) + ? SetOfProducedProducer.class + : SetProducer.class; + Snippet initializeSetSnippet = + Snippet.format( + "%s.create(%s)", + ClassName.fromClass(factoryClass), + makeParametersSnippet(parameterSnippets.build())); + initializationSnippets.add( + initializeMember(resolvedBindings.bindingKey(), initializeSetSnippet)); + + return Snippet.concat(initializationSnippets.build()); + } + + private Snippet initializeMapMultibindings(ResolvedBindings resolvedBindings) { + ImmutableList.Builder<Snippet> initializationSnippets = ImmutableList.builder(); + + if (any(resolvedBindings.contributionBindings(), Binding.Type.PRODUCTION)) { + // TODO(beder): Implement producer map bindings. + throw new IllegalStateException("producer map bindings not implemented yet"); + } + for (ContributionBinding binding : resolvedBindings.contributionBindings()) { + Optional<MemberSelect> multibindingContributionSnippet = + getMultibindingContributionSnippet(binding); + if (!isMapWithNonProvidedValues(binding.key().type()) + && multibindingContributionSnippet.isPresent() + && multibindingContributionSnippet.get().owningClass().equals(name)) { + initializationSnippets.add( + Snippet.format( + "this.%s = %s;", + multibindingContributionSnippet.get().getSnippetFor(name), + initializeFactoryForContributionBinding(binding))); + } + } + initializationSnippets.add( + initializeMember( + resolvedBindings.bindingKey(), + initializeMapBinding(resolvedBindings.contributionBindings()))); + + return Snippet.concat(initializationSnippets.build()); + } + + private Snippet initializeUniqueContributionBinding(ResolvedBindings resolvedBindings) { + ImmutableList.Builder<Snippet> initializationSnippets = ImmutableList.builder(); + + ContributionBinding binding = getOnlyElement(resolvedBindings.ownedContributionBindings()); + if (!binding.factoryCreationStrategy().equals(ENUM_INSTANCE) || binding.scope().isPresent()) { + initializationSnippets.add(initializeDelegateFactories(binding)); + initializationSnippets.add( + initializeMember( + resolvedBindings.bindingKey(), initializeFactoryForContributionBinding(binding))); + } + + return Snippet.concat(initializationSnippets.build()); + } + + private Snippet initializeMembersInjectionBinding(ResolvedBindings resolvedBindings) { + ImmutableList.Builder<Snippet> initializationSnippets = ImmutableList.builder(); + + MembersInjectionBinding binding = resolvedBindings.membersInjectionBinding().get(); + if (!binding.injectionStrategy().equals(MembersInjectionBinding.Strategy.NO_OP)) { + initializationSnippets.add(initializeDelegateFactories(binding)); + initializationSnippets.add( + initializeMember( + resolvedBindings.bindingKey(), initializeMembersInjectorForBinding(binding))); } + + return Snippet.concat(initializationSnippets.build()); } - private void initializeDelegateFactories(Binding binding, MethodWriter initializeMethod) { + private Snippet initializeDelegateFactories(Binding binding) { + ImmutableList.Builder<Snippet> initializationSnippets = ImmutableList.builder(); + for (Collection<DependencyRequest> requestsForKey : indexDependenciesByUnresolvedKey(types, binding.dependencies()).asMap().values()) { BindingKey dependencyKey = @@ -840,37 +845,40 @@ abstract class AbstractComponentWriter { .toSet()); if (!getMemberSelect(dependencyKey).staticMember() && getInitializationState(dependencyKey).equals(UNINITIALIZED)) { - initializeMethod - .body() - .addSnippet( + initializationSnippets.add( + Snippet.format( "this.%s = new %s();", getMemberSelectSnippet(dependencyKey), - ClassName.fromClass(DelegateFactory.class)); + ClassName.fromClass(DelegateFactory.class))); setInitializationState(dependencyKey, DELEGATED); } } + + return Snippet.concat(initializationSnippets.build()); } - private void initializeMember( - MethodWriter initializeMethod, BindingKey bindingKey, Snippet initializationSnippet) { + private Snippet initializeMember(BindingKey bindingKey, Snippet initializationSnippet) { + ImmutableList.Builder<Snippet> initializationSnippets = ImmutableList.builder(); + Snippet memberSelect = getMemberSelectSnippet(bindingKey); Snippet delegateFactoryVariable = delegateFactoryVariableSnippet(bindingKey); if (getInitializationState(bindingKey).equals(DELEGATED)) { - initializeMethod - .body() - .addSnippet( + initializationSnippets.add( + Snippet.format( "%1$s %2$s = (%1$s) %3$s;", ClassName.fromClass(DelegateFactory.class), delegateFactoryVariable, - memberSelect); + memberSelect)); } - initializeMethod.body().addSnippet("this.%s = %s;", memberSelect, initializationSnippet); + initializationSnippets.add( + Snippet.format("this.%s = %s;", memberSelect, initializationSnippet)); if (getInitializationState(bindingKey).equals(DELEGATED)) { - initializeMethod - .body() - .addSnippet("%s.setDelegatedProvider(%s);", delegateFactoryVariable, memberSelect); + initializationSnippets.add( + Snippet.format("%s.setDelegatedProvider(%s);", delegateFactoryVariable, memberSelect)); } setInitializationState(bindingKey, INITIALIZED); + + return Snippet.concat(initializationSnippets.build()); } private Snippet delegateFactoryVariableSnippet(BindingKey key) { @@ -878,16 +886,6 @@ abstract class AbstractComponentWriter { } private Snippet initializeFactoryForContributionBinding(ContributionBinding binding) { - if (binding instanceof ProvisionBinding) { - return initializeFactoryForProvisionBinding((ProvisionBinding) binding); - } else if (binding instanceof ProductionBinding) { - return initializeFactoryForProductionBinding((ProductionBinding) binding); - } else { - throw new AssertionError(); - } - } - - private Snippet initializeFactoryForProvisionBinding(ProvisionBinding binding) { TypeName bindingKeyTypeName = TypeNames.forTypeMirror(binding.key().type()); switch (binding.bindingKind()) { case COMPONENT: @@ -898,32 +896,17 @@ abstract class AbstractComponentWriter { bindingKeyTypeName.equals(componentDefinitionTypeName()) ? "this" : getComponentContributionSnippet(MoreTypes.asTypeElement(binding.key().type()))); + case COMPONENT_PROVISION: - TypeElement bindingTypeElement = - graph.componentDescriptor().dependencyMethodIndex().get(binding.bindingElement()); - if (binding.nullableType().isPresent() - || nullableValidationType.equals(Diagnostic.Kind.WARNING)) { - Snippet nullableSnippet = - binding.nullableType().isPresent() - ? Snippet.format("@%s ", TypeNames.forTypeMirror(binding.nullableType().get())) - : Snippet.format(""); - return Snippet.format( - Joiner.on('\n') - .join( - "new %1$s<%2$s>() {", - " private final %6$s %7$s = %3$s;", - " %5$s@Override public %2$s get() {", - " return %7$s.%4$s();", - " }", - "}"), - /* 1 */ ClassName.fromClass(Factory.class), - /* 2 */ bindingKeyTypeName, - /* 3 */ getComponentContributionSnippet(bindingTypeElement), - /* 4 */ binding.bindingElement().getSimpleName().toString(), - /* 5 */ nullableSnippet, - /* 6 */ TypeNames.forTypeMirror(bindingTypeElement.asType()), - /* 7 */ simpleVariableName(bindingTypeElement)); - } else { + { + TypeElement bindingTypeElement = + graph.componentDescriptor().dependencyMethodIndex().get(binding.bindingElement()); + String localFactoryVariable = simpleVariableName(bindingTypeElement); + Snippet callFactoryMethodSnippet = + Snippet.format( + "%s.%s()", + localFactoryVariable, + binding.bindingElement().getSimpleName().toString()); // TODO(sameb): This throws a very vague NPE right now. The stack trace doesn't // help to figure out what the method or return type is. If we include a string // of the return type or method name in the error message, that can defeat obfuscation. @@ -932,95 +915,126 @@ abstract class AbstractComponentWriter { // What should we do? StringLiteral failMsg = StringLiteral.forValue(CANNOT_RETURN_NULL_FROM_NON_NULLABLE_COMPONENT_METHOD); + Snippet getMethodBody = + binding.nullableType().isPresent() + || nullableValidationType.equals(Diagnostic.Kind.WARNING) + ? Snippet.format("return %s;", callFactoryMethodSnippet) + : Snippet.format( + Joiner.on('\n') + .join( + "%s provided = %s;", + "if (provided == null) {", + " throw new NullPointerException(%s);", + "}", + "return provided;"), + bindingKeyTypeName, + callFactoryMethodSnippet, + failMsg); return Snippet.format( Joiner.on('\n') .join( "new %1$s<%2$s>() {", - " private final %6$s %7$s = %3$s;", - " @Override public %2$s get() {", - " %2$s provided = %7$s.%4$s();", - " if (provided == null) {", - " throw new NullPointerException(%5$s);", - " }", - " return provided;", + " private final %5$s %6$s = %3$s;", + " %4$s@Override public %2$s get() {", + " %7$s", " }", "}"), /* 1 */ ClassName.fromClass(Factory.class), /* 2 */ bindingKeyTypeName, /* 3 */ getComponentContributionSnippet(bindingTypeElement), - /* 4 */ binding.bindingElement().getSimpleName().toString(), - /* 5 */ failMsg, - /* 6 */ TypeNames.forTypeMirror(bindingTypeElement.asType()), - /* 7 */ simpleVariableName(bindingTypeElement)); + /* 4 */ nullableSnippet(binding.nullableType()), + /* 5 */ TypeNames.forTypeMirror(bindingTypeElement.asType()), + /* 6 */ localFactoryVariable, + /* 7 */ getMethodBody); } - case INJECTION: - case PROVISION: - List<Snippet> parameters = - Lists.newArrayListWithCapacity(binding.dependencies().size() + 1); - if (binding.bindingKind().equals(PROVISION) - && !binding.bindingElement().getModifiers().contains(STATIC)) { - parameters.add(getComponentContributionSnippet(binding.contributedBy().get())); - } - parameters.addAll(getDependencyParameters(binding)); - Snippet factorySnippet = - Snippet.format( - "%s.create(%s)", - factoryNameForProvisionBinding(binding), - Snippet.makeParametersSnippet(parameters)); - return binding.scope().isPresent() - ? Snippet.format( - "%s.create(%s)", ClassName.fromClass(ScopedProvider.class), factorySnippet) - : factorySnippet; - default: - throw new AssertionError(); - } - } - - private Snippet initializeFactoryForProductionBinding(ProductionBinding binding) { - switch (binding.bindingKind()) { - case COMPONENT_PRODUCTION: - TypeElement bindingTypeElement = - graph.componentDescriptor().dependencyMethodIndex().get(binding.bindingElement()); + case SUBCOMPONENT_BUILDER: return Snippet.format( Joiner.on('\n') .join( "new %1$s<%2$s>() {", - " private final %6$s %7$s = %4$s;", - " @Override public %3$s<%2$s> get() {", - " return %7$s.%5$s();", + " @Override public %2$s get() {", + " return %3$s();", " }", "}"), - /* 1 */ ClassName.fromClass(Producer.class), - /* 2 */ TypeNames.forTypeMirror(binding.key().type()), - /* 3 */ ClassName.fromClass(ListenableFuture.class), - /* 4 */ getComponentContributionSnippet(bindingTypeElement), - /* 5 */ binding.bindingElement().getSimpleName().toString(), - /* 6 */ TypeNames.forTypeMirror(bindingTypeElement.asType()), - /* 7 */ simpleVariableName(bindingTypeElement)); + /* 1 */ ClassName.fromClass(Factory.class), + /* 2 */ bindingKeyTypeName, + /* 3 */ binding.bindingElement().getSimpleName().toString()); + + case INJECTION: + case PROVISION: + { + List<Snippet> parameters = + Lists.newArrayListWithCapacity(binding.dependencies().size() + 1); + if (binding.bindingKind().equals(PROVISION) + && !binding.bindingElement().getModifiers().contains(STATIC)) { + parameters.add(getComponentContributionSnippet(binding.contributedBy().get())); + } + parameters.addAll(getDependencyParameters(binding)); + + Snippet factorySnippet = + Snippet.format( + "%s.create(%s)", + generatedClassNameForBinding(binding), + Snippet.makeParametersSnippet(parameters)); + return binding.scope().isPresent() + ? Snippet.format( + "%s.create(%s)", ClassName.fromClass(ScopedProvider.class), factorySnippet) + : factorySnippet; + } + + case COMPONENT_PRODUCTION: + { + TypeElement bindingTypeElement = + graph.componentDescriptor().dependencyMethodIndex().get(binding.bindingElement()); + return Snippet.format( + Joiner.on('\n') + .join( + "new %1$s<%2$s>() {", + " private final %6$s %7$s = %4$s;", + " @Override public %3$s<%2$s> get() {", + " return %7$s.%5$s();", + " }", + "}"), + /* 1 */ ClassName.fromClass(Producer.class), + /* 2 */ TypeNames.forTypeMirror(binding.key().type()), + /* 3 */ ClassName.fromClass(ListenableFuture.class), + /* 4 */ getComponentContributionSnippet(bindingTypeElement), + /* 5 */ binding.bindingElement().getSimpleName().toString(), + /* 6 */ TypeNames.forTypeMirror(bindingTypeElement.asType()), + /* 7 */ simpleVariableName(bindingTypeElement)); + } + case IMMEDIATE: case FUTURE_PRODUCTION: - List<Snippet> parameters = - Lists.newArrayListWithCapacity(binding.dependencies().size() + 3); - // TODO(beder): Pass the actual ProductionComponentMonitor. - parameters.add(Snippet.format("null")); - if (!binding.bindingElement().getModifiers().contains(STATIC)) { - parameters.add(getComponentContributionSnippet(binding.bindingTypeElement())); + { + List<Snippet> parameters = + Lists.newArrayListWithCapacity(binding.implicitDependencies().size() + 2); + if (!binding.bindingElement().getModifiers().contains(STATIC)) { + parameters.add(getComponentContributionSnippet(binding.bindingTypeElement())); + } + parameters.add( + getComponentContributionSnippet( + graph.componentDescriptor().executorDependency().get())); + parameters.addAll(getProducerDependencyParameters(binding)); + + return Snippet.format( + "new %s(%s)", + generatedClassNameForBinding(binding), + Snippet.makeParametersSnippet(parameters)); } - parameters.add( - getComponentContributionSnippet( - graph.componentDescriptor().executorDependency().get())); - parameters.addAll(getProducerDependencyParameters(binding)); - return Snippet.format( - "new %s(%s)", - factoryNameForProductionBinding(binding), - Snippet.makeParametersSnippet(parameters)); default: throw new AssertionError(); } } + private Snippet nullableSnippet(Optional<DeclaredType> nullableType) { + return nullableType.isPresent() + ? Snippet.format("@%s ", TypeNames.forTypeMirror(nullableType.get())) + : Snippet.format(""); + } + private Snippet initializeMembersInjectorForBinding(MembersInjectionBinding binding) { switch (binding.injectionStrategy()) { case NO_OP: @@ -1076,8 +1090,9 @@ abstract class AbstractComponentWriter { private List<Snippet> getProducerDependencyParameters(Binding binding) { ImmutableList.Builder<Snippet> parameters = ImmutableList.builder(); for (Collection<DependencyRequest> requestsForKey : - SourceFiles.indexDependenciesByUnresolvedKey( - types, binding.dependencies()).asMap().values()) { + SourceFiles.indexDependenciesByUnresolvedKey(types, binding.implicitDependencies()) + .asMap() + .values()) { BindingKey key = Iterables.getOnlyElement(FluentIterable.from(requestsForKey) .transform(DependencyRequest.BINDING_KEY_FUNCTION)); ResolvedBindings resolvedBindings = graph.resolvedBindings().get(key); @@ -1097,9 +1112,9 @@ abstract class AbstractComponentWriter { return parameters.build(); } - private Snippet initializeMapBinding(Set<ProvisionBinding> bindings) { + private Snippet initializeMapBinding(Set<ContributionBinding> bindings) { // Get type information from the first binding. - ProvisionBinding firstBinding = bindings.iterator().next(); + ContributionBinding firstBinding = bindings.iterator().next(); DeclaredType mapType = asDeclared(firstBinding.key().type()); if (isMapWithNonProvidedValues(mapType)) { @@ -1117,7 +1132,7 @@ abstract class AbstractComponentWriter { TypeNames.forTypeMirror(getProvidedValueTypeOfMap(mapType)), // V of Map<K, Provider<V>> bindings.size())); - for (ProvisionBinding binding : bindings) { + for (ContributionBinding binding : bindings) { snippets.add( Snippet.format( " .put(%s, %s)", @@ -1127,7 +1142,7 @@ abstract class AbstractComponentWriter { snippets.add(Snippet.format(" .build()")); - return Snippet.join(Joiner.on('\n'), snippets.build()); + return Snippet.concat(snippets.build()); } private static String simpleVariableName(TypeElement typeElement) { diff --git a/compiler/src/main/java/dagger/internal/codegen/Binding.java b/compiler/src/main/java/dagger/internal/codegen/Binding.java index 29f17b3c5..0a6b84052 100644 --- a/compiler/src/main/java/dagger/internal/codegen/Binding.java +++ b/compiler/src/main/java/dagger/internal/codegen/Binding.java @@ -17,11 +17,15 @@ package dagger.internal.codegen; import com.google.auto.common.MoreElements; import com.google.common.base.Optional; +import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import dagger.MembersInjector; +import dagger.producers.Producer; import java.util.List; import java.util.Set; +import javax.inject.Provider; import javax.lang.model.element.Element; import javax.lang.model.element.ElementVisitor; import javax.lang.model.element.Name; @@ -48,6 +52,59 @@ import static javax.lang.model.element.Modifier.PUBLIC; * @since 2.0 */ abstract class Binding { + + /** + * The subtype of this binding. + */ + enum Type implements Predicate<Binding> { + /** A binding with this type is a {@link ProvisionBinding}. */ + PROVISION(Provider.class), + /** A binding with this type is a {@link MembersInjectionBinding}. */ + MEMBERS_INJECTION(MembersInjector.class), + /** A binding with this type is a {@link ProductionBinding}. */ + PRODUCTION(Producer.class), + ; + + private final Class<?> frameworkClass; + + private Type(Class<?> frameworkClass) { + this.frameworkClass = frameworkClass; + } + + /** + * Returns the framework class associated with bindings of this type. + */ + Class<?> frameworkClass() { + return frameworkClass; + } + + BindingKey.Kind bindingKeyKind() { + switch (this) { + case MEMBERS_INJECTION: + return BindingKey.Kind.MEMBERS_INJECTION; + case PROVISION: + case PRODUCTION: + return BindingKey.Kind.CONTRIBUTION; + default: + throw new AssertionError(); + } + } + + @Override + public boolean apply(Binding binding) { + return this.equals(binding.bindingType()); + } + } + + abstract Binding.Type bindingType(); + + /** + * Returns the framework class associated with this binding. + */ + Class<?> frameworkClass() { + return bindingType().frameworkClass(); + } + static Optional<String> bindingPackageFor(Iterable<? extends Binding> bindings) { ImmutableSet.Builder<String> bindingPackagesBuilder = ImmutableSet.builder(); for (Binding binding : bindings) { @@ -67,6 +124,10 @@ abstract class Binding { /** The {@link Key} that is provided by this binding. */ protected abstract Key key(); + BindingKey bindingKey() { + return BindingKey.create(bindingType().bindingKeyKind(), key()); + } + /** Returns the {@link Element} instance that is responsible for declaring the binding. */ abstract Element bindingElement(); @@ -164,6 +225,13 @@ abstract class Binding { */ abstract boolean hasNonDefaultTypeParameters(); + /** + * The scope of this binding. + */ + Scope scope() { + return Scope.unscoped(); + } + // TODO(sameb): Remove the TypeElement parameter and pull it from the TypeMirror. static boolean hasNonDefaultTypeParameters(TypeElement element, TypeMirror type, Types types) { // If the element has no type parameters, nothing can be wrong. diff --git a/compiler/src/main/java/dagger/internal/codegen/BindingGraph.java b/compiler/src/main/java/dagger/internal/codegen/BindingGraph.java index f170f3988..b95143803 100644 --- a/compiler/src/main/java/dagger/internal/codegen/BindingGraph.java +++ b/compiler/src/main/java/dagger/internal/codegen/BindingGraph.java @@ -26,13 +26,16 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.collect.TreeTraverser; import dagger.Component; import dagger.Subcomponent; +import dagger.internal.codegen.Binding.Type; import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor; +import dagger.producers.Producer; import dagger.producers.ProductionComponent; import java.util.ArrayDeque; import java.util.Collection; @@ -45,6 +48,7 @@ import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import javax.inject.Inject; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; @@ -54,9 +58,13 @@ import javax.lang.model.util.Elements; import static com.google.auto.common.MoreElements.getAnnotationMirror; import static com.google.common.base.Predicates.in; import static com.google.common.base.Verify.verify; +import static com.google.common.collect.Iterables.any; +import static com.google.common.collect.Sets.union; import static dagger.internal.codegen.BindingKey.Kind.CONTRIBUTION; import static dagger.internal.codegen.ComponentDescriptor.isComponentContributionMethod; import static dagger.internal.codegen.ComponentDescriptor.isComponentProductionMethod; +import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor.isOfKind; +import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodKind.SUBCOMPONENT_BUILDER; import static dagger.internal.codegen.ComponentDescriptor.Kind.PRODUCTION_COMPONENT; import static dagger.internal.codegen.ConfigurationAnnotations.getComponentDependencies; import static dagger.internal.codegen.MembersInjectionBinding.Strategy.INJECT_MEMBERS; @@ -104,29 +112,33 @@ abstract class BindingGraph { * {@link ProductionComponent}. */ ImmutableSet<TypeElement> componentRequirements() { - return SUBGRAPH_TRAVERSER.preOrderTraversal(this) - .transformAndConcat(new Function<BindingGraph, Iterable<ResolvedBindings>>() { - @Override - public Iterable<ResolvedBindings> apply(BindingGraph input) { - return input.resolvedBindings().values(); - } - }) - .transformAndConcat(new Function<ResolvedBindings, Set<? extends ContributionBinding>>() { - @Override - public Set<? extends ContributionBinding> apply(ResolvedBindings input) { - return (input.bindingKey().kind().equals(CONTRIBUTION)) - ? input.contributionBindings() - : ImmutableSet.<ContributionBinding>of(); - } - }) - .transformAndConcat(new Function<ContributionBinding, Set<TypeElement>>() { - @Override - public Set<TypeElement> apply(ContributionBinding input) { - return input.bindingElement().getModifiers().contains(STATIC) - ? ImmutableSet.<TypeElement>of() - : input.contributedBy().asSet(); - } - }) + return SUBGRAPH_TRAVERSER + .preOrderTraversal(this) + .transformAndConcat( + new Function<BindingGraph, Iterable<ResolvedBindings>>() { + @Override + public Iterable<ResolvedBindings> apply(BindingGraph input) { + return input.resolvedBindings().values(); + } + }) + .transformAndConcat( + new Function<ResolvedBindings, Set<ContributionBinding>>() { + @Override + public Set<ContributionBinding> apply(ResolvedBindings input) { + return (input.bindingKey().kind().equals(CONTRIBUTION)) + ? input.contributionBindings() + : ImmutableSet.<ContributionBinding>of(); + } + }) + .transformAndConcat( + new Function<ContributionBinding, Set<TypeElement>>() { + @Override + public Set<TypeElement> apply(ContributionBinding input) { + return input.bindingElement().getModifiers().contains(STATIC) + ? ImmutableSet.<TypeElement>of() + : input.contributedBy().asSet(); + } + }) .filter(in(ownedModuleTypes())) .append(componentDescriptor().dependencies()) .append(componentDescriptor().executorDependency().asSet()) @@ -145,20 +157,17 @@ abstract class BindingGraph { private final Elements elements; private final InjectBindingRegistry injectBindingRegistry; private final Key.Factory keyFactory; - private final DependencyRequest.Factory dependencyRequestFactory; private final ProvisionBinding.Factory provisionBindingFactory; private final ProductionBinding.Factory productionBindingFactory; Factory(Elements elements, InjectBindingRegistry injectBindingRegistry, Key.Factory keyFactory, - DependencyRequest.Factory dependencyRequestFactory, ProvisionBinding.Factory provisionBindingFactory, ProductionBinding.Factory productionBindingFactory) { this.elements = elements; this.injectBindingRegistry = injectBindingRegistry; this.keyFactory = keyFactory; - this.dependencyRequestFactory = dependencyRequestFactory; this.provisionBindingFactory = provisionBindingFactory; this.productionBindingFactory = productionBindingFactory; } @@ -169,16 +178,11 @@ abstract class BindingGraph { private BindingGraph create( Optional<Resolver> parentResolver, ComponentDescriptor componentDescriptor) { - ImmutableSet.Builder<ProvisionBinding> explicitProvisionBindingsBuilder = - ImmutableSet.builder(); - ImmutableSet.Builder<ProductionBinding> explicitProductionBindingsBuilder = - ImmutableSet.builder(); + ImmutableSet.Builder<ContributionBinding> explicitBindingsBuilder = ImmutableSet.builder(); // binding for the component itself TypeElement componentDefinitionType = componentDescriptor.componentDefinitionType(); - ProvisionBinding componentBinding = - provisionBindingFactory.forComponent(componentDefinitionType); - explicitProvisionBindingsBuilder.add(componentBinding); + explicitBindingsBuilder.add(provisionBindingFactory.forComponent(componentDefinitionType)); // Collect Component dependencies. Optional<AnnotationMirror> componentMirror = @@ -188,34 +192,35 @@ abstract class BindingGraph { ? MoreTypes.asTypeElements(getComponentDependencies(componentMirror.get())) : ImmutableSet.<TypeElement>of(); for (TypeElement componentDependency : componentDependencyTypes) { - explicitProvisionBindingsBuilder.add( - provisionBindingFactory.forComponent(componentDependency)); + explicitBindingsBuilder.add(provisionBindingFactory.forComponent(componentDependency)); List<ExecutableElement> dependencyMethods = ElementFilter.methodsIn(elements.getAllMembers(componentDependency)); for (ExecutableElement method : dependencyMethods) { // MembersInjection methods aren't "provided" explicitly, so ignore them. if (isComponentContributionMethod(elements, method)) { - if (componentDescriptor.kind().equals(PRODUCTION_COMPONENT) - && isComponentProductionMethod(elements, method)) { - explicitProductionBindingsBuilder.add( - productionBindingFactory.forComponentMethod(method)); - } else { - explicitProvisionBindingsBuilder.add( - provisionBindingFactory.forComponentMethod(method)); - } + explicitBindingsBuilder.add( + componentDescriptor.kind().equals(PRODUCTION_COMPONENT) + && isComponentProductionMethod(elements, method) + ? productionBindingFactory.forComponentMethod(method) + : provisionBindingFactory.forComponentMethod(method)); } } } + // Bindings for subcomponent builders. + for (ComponentMethodDescriptor subcomponentMethodDescriptor : + Iterables.filter( + componentDescriptor.subcomponents().keySet(), isOfKind(SUBCOMPONENT_BUILDER))) { + explicitBindingsBuilder.add( + provisionBindingFactory.forSubcomponentBuilderMethod( + subcomponentMethodDescriptor.methodElement(), + componentDescriptor.componentDefinitionType())); + } + // Collect transitive module bindings. for (ModuleDescriptor moduleDescriptor : componentDescriptor.transitiveModules()) { for (ContributionBinding binding : moduleDescriptor.bindings()) { - if (binding instanceof ProvisionBinding) { - explicitProvisionBindingsBuilder.add((ProvisionBinding) binding); - } - if (binding instanceof ProductionBinding) { - explicitProductionBindingsBuilder.add((ProductionBinding) binding); - } + explicitBindingsBuilder.add(binding); } } @@ -223,8 +228,7 @@ abstract class BindingGraph { new Resolver( parentResolver, componentDescriptor, - explicitBindingsByKey(explicitProvisionBindingsBuilder.build()), - explicitBindingsByKey(explicitProductionBindingsBuilder.build())); + explicitBindingsByKey(explicitBindingsBuilder.build())); for (ComponentMethodDescriptor componentMethod : componentDescriptor.componentMethods()) { Optional<DependencyRequest> componentMethodRequest = componentMethod.dependencyRequest(); if (componentMethodRequest.isPresent()) { @@ -269,9 +273,8 @@ abstract class BindingGraph { private final class Resolver { final Optional<Resolver> parentResolver; final ComponentDescriptor componentDescriptor; - final ImmutableSetMultimap<Key, ProvisionBinding> explicitProvisionBindings; - final ImmutableSet<ProvisionBinding> explicitProvisionBindingsSet; - final ImmutableSetMultimap<Key, ProductionBinding> explicitProductionBindings; + final ImmutableSetMultimap<Key, ContributionBinding> explicitBindings; + final ImmutableSet<ContributionBinding> explicitBindingsSet; final Map<BindingKey, ResolvedBindings> resolvedBindings; final Deque<BindingKey> cycleStack = new ArrayDeque<>(); final Cache<BindingKey, Boolean> dependsOnLocalMultibindingsCache = @@ -280,134 +283,132 @@ abstract class BindingGraph { Resolver( Optional<Resolver> parentResolver, ComponentDescriptor componentDescriptor, - ImmutableSetMultimap<Key, ProvisionBinding> explicitProvisionBindings, - ImmutableSetMultimap<Key, ProductionBinding> explicitProductionBindings) { + ImmutableSetMultimap<Key, ContributionBinding> explicitBindings) { assert parentResolver != null; this.parentResolver = parentResolver; assert componentDescriptor != null; this.componentDescriptor = componentDescriptor; - assert explicitProvisionBindings != null; - this.explicitProvisionBindings = explicitProvisionBindings; - this.explicitProvisionBindingsSet = ImmutableSet.copyOf(explicitProvisionBindings.values()); - assert explicitProductionBindings != null; - this.explicitProductionBindings = explicitProductionBindings; + assert explicitBindings != null; + this.explicitBindings = explicitBindings; + this.explicitBindingsSet = ImmutableSet.copyOf(explicitBindings.values()); this.resolvedBindings = Maps.newLinkedHashMap(); } /** - * Looks up the bindings associated with a given dependency request and returns them. In the - * event that the binding is owned by a parent component it will trigger resolution in that - * component's resolver but will return an {@link Optional#absent} value. + * Looks up the bindings associated with a given dependency request and returns them. + * + * <p>Requests for {@code Map<K, V>} for which there are only bindings for + * {@code Map<K, Provider<V>>} will resolve to a single implicit binding for the latter map + * (and similarly for {@link Producer}s). + * + * <p>If there are no explicit bindings for a contribution, looks for implicit + * {@link Inject @Inject}-annotated constructor types. */ ResolvedBindings lookUpBindings(DependencyRequest request) { BindingKey bindingKey = request.bindingKey(); switch (bindingKey.kind()) { case CONTRIBUTION: // First, check for explicit keys (those from modules and components) - ImmutableSet<ProvisionBinding> explicitProvisionBindingsForKey = - getExplicitProvisionBindings(bindingKey.key()); - ImmutableSet<ProductionBinding> explicitProductionBindingsForKey = - getExplicitProductionBindings(bindingKey.key()); + ImmutableSet<ContributionBinding> explicitBindingsForKey = + getExplicitBindings(bindingKey.key()); // If the key is Map<K, V>, get its implicit binding keys, which are either // Map<K, Provider<V>> or Map<K, Producer<V>>, and grab their explicit bindings. Optional<Key> mapProviderKey = keyFactory.implicitMapProviderKeyFrom(bindingKey.key()); - ImmutableSet<ProvisionBinding> explicitMapProvisionBindings = ImmutableSet.of(); + ImmutableSet.Builder<ContributionBinding> explicitMapBindingsBuilder = + ImmutableSet.builder(); if (mapProviderKey.isPresent()) { - explicitMapProvisionBindings = getExplicitProvisionBindings(mapProviderKey.get()); + explicitMapBindingsBuilder.addAll(getExplicitBindings(mapProviderKey.get())); } Optional<Key> mapProducerKey = keyFactory.implicitMapProducerKeyFrom(bindingKey.key()); - ImmutableSet<ProductionBinding> explicitMapProductionBindings = ImmutableSet.of(); if (mapProducerKey.isPresent()) { - explicitMapProductionBindings = getExplicitProductionBindings(mapProducerKey.get()); + explicitMapBindingsBuilder.addAll(getExplicitBindings(mapProducerKey.get())); } - - if (!explicitProvisionBindingsForKey.isEmpty() - || !explicitProductionBindingsForKey.isEmpty()) { - // we have some explicit binding for this key, so we collect all explicit implicit map - // bindings that might conflict with this and let the validator sort it out - ImmutableSet.Builder<ContributionBinding> ownedBindings = ImmutableSet.builder(); - ImmutableSetMultimap.Builder<ComponentDescriptor, ContributionBinding> - inheritedBindings = ImmutableSetMultimap.builder(); - for (ProvisionBinding provisionBinding : - Sets.union(explicitProvisionBindingsForKey, explicitMapProvisionBindings)) { - if (isResolvedInParent(request, provisionBinding) - && !shouldOwnParentBinding(request, provisionBinding)) { - inheritedBindings.put( - getOwningResolver(provisionBinding).get().componentDescriptor, - provisionBinding); - } else { - ownedBindings.add(provisionBinding); - } + ImmutableSet<ContributionBinding> explicitMapBindings = + explicitMapBindingsBuilder.build(); + + // If the key is Set<Produced<T>>, then we look up bindings by the alternate key Set<T>. + Optional<Key> setKeyFromProduced = + keyFactory.implicitSetKeyFromProduced(bindingKey.key()); + ImmutableSet<ContributionBinding> explicitSetBindings = + setKeyFromProduced.isPresent() + ? getExplicitBindings(setKeyFromProduced.get()) + : ImmutableSet.<ContributionBinding>of(); + + if (!explicitBindingsForKey.isEmpty() || !explicitSetBindings.isEmpty()) { + /* If there are any explicit bindings for this key, then combine those with any + * conflicting Map<K, Provider<V>> bindings and let the validator fail. */ + ImmutableSetMultimap.Builder<ComponentDescriptor, ContributionBinding> bindings = + ImmutableSetMultimap.builder(); + for (ContributionBinding binding : + union(explicitBindingsForKey, union(explicitSetBindings, explicitMapBindings))) { + bindings.put(getOwningComponent(request, binding), binding); } - return ResolvedBindings.create(bindingKey, + return ResolvedBindings.forContributionBindings( + bindingKey, componentDescriptor, bindings.build()); + } else if (any(explicitMapBindings, Binding.Type.PRODUCTION)) { + /* If this binding is for Map<K, V> and there are no explicit Map<K, V> bindings but + * some explicit Map<K, Producer<V>> bindings, then this binding must have only the + * implicit dependency on Map<K, Producer<V>>. */ + return ResolvedBindings.forContributionBindings( + bindingKey, + componentDescriptor, + productionBindingFactory.implicitMapOfProducerBinding(request)); + } else if (any(explicitMapBindings, Binding.Type.PROVISION)) { + /* If this binding is for Map<K, V> and there are no explicit Map<K, V> bindings but + * some explicit Map<K, Provider<V>> bindings, then this binding must have only the + * implicit dependency on Map<K, Provider<V>>. */ + return ResolvedBindings.forContributionBindings( + bindingKey, componentDescriptor, - ownedBindings - .addAll(explicitProductionBindingsForKey) - .addAll(explicitMapProductionBindings) - .build(), - inheritedBindings.build()); + provisionBindingFactory.implicitMapOfProviderBinding(request)); } else { - if (!explicitMapProductionBindings.isEmpty()) { - // if we have any explicit Map<K, Producer<V>> bindings, then this Map<K, V> binding - // must be considered an implicit ProductionBinding - DependencyRequest implicitRequest = - dependencyRequestFactory.forImplicitMapBinding(request, mapProducerKey.get()); - return ResolvedBindings.create( - bindingKey, - componentDescriptor, - productionBindingFactory.forImplicitMapBinding(request, implicitRequest)); - } else if (!explicitMapProvisionBindings.isEmpty()) { - // if there are Map<K, Provider<V>> bindings, then it'll be an implicit - // ProvisionBinding - DependencyRequest implicitRequest = - dependencyRequestFactory.forImplicitMapBinding(request, mapProviderKey.get()); - return ResolvedBindings.create( - bindingKey, - componentDescriptor, - provisionBindingFactory.forImplicitMapBinding(request, implicitRequest)); - } else { - // no explicit binding, look it up. - Optional<ProvisionBinding> provisionBinding = - injectBindingRegistry.getOrFindProvisionBinding(bindingKey.key()); - if (provisionBinding.isPresent()) { - if (isResolvedInParent(request, provisionBinding.get()) - && !shouldOwnParentBinding(request, provisionBinding.get())) { - return ResolvedBindings.create( - bindingKey, - componentDescriptor, - ImmutableSet.<Binding>of(), - ImmutableSetMultimap.of( - getOwningResolver(provisionBinding.get()).get().componentDescriptor, - provisionBinding.get())); - } - } - return ResolvedBindings.create( - bindingKey, - componentDescriptor, - provisionBinding.asSet(), - ImmutableSetMultimap.<ComponentDescriptor, Binding>of()); - } + /* If there are no explicit bindings at all, look for an implicit @Inject-constructed + * binding. */ + Optional<ProvisionBinding> provisionBinding = + injectBindingRegistry.getOrFindProvisionBinding(bindingKey.key()); + ComponentDescriptor owningComponent = + provisionBinding.isPresent() + && isResolvedInParent(request, provisionBinding.get()) + && !shouldOwnParentBinding(request, provisionBinding.get()) + ? getOwningResolver(provisionBinding.get()).get().componentDescriptor + : componentDescriptor; + return ResolvedBindings.forContributionBindings( + bindingKey, + componentDescriptor, + ImmutableSetMultimap.<ComponentDescriptor, ContributionBinding>builder() + .putAll(owningComponent, provisionBinding.asSet()) + .build()); } + case MEMBERS_INJECTION: // no explicit deps for members injection, so just look it up - return ResolvedBindings.create( - bindingKey, - componentDescriptor, - rollUpMembersInjectionBindings(bindingKey.key())); + return ResolvedBindings.forMembersInjectionBinding( + bindingKey, componentDescriptor, rollUpMembersInjectionBindings(bindingKey.key())); default: throw new AssertionError(); } } /** - * Returns {@code true} if {@code provisionBinding} is owned by a parent resolver. If so, - * calls {@link #resolve(DependencyRequest) resolve(request)} on that resolver. + * If {@code binding} should be owned by a parent component, resolves the binding in that + * component's resolver and returns that component. Otherwise returns the component for this + * resolver. + */ + private ComponentDescriptor getOwningComponent( + DependencyRequest request, ContributionBinding binding) { + return isResolvedInParent(request, binding) && !shouldOwnParentBinding(request, binding) + ? getOwningResolver(binding).get().componentDescriptor + : componentDescriptor; + } + + /** + * Returns {@code true} if {@code binding} is owned by a parent resolver. If so, calls + * {@link #resolve(DependencyRequest) resolve(request)} on that resolver. */ - private boolean isResolvedInParent( - DependencyRequest request, ProvisionBinding provisionBinding) { - Optional<Resolver> owningResolver = getOwningResolver(provisionBinding); + private boolean isResolvedInParent(DependencyRequest request, ContributionBinding binding) { + Optional<Resolver> owningResolver = getOwningResolver(binding); if (owningResolver.isPresent() && !owningResolver.get().equals(this)) { owningResolver.get().resolve(request); return true; @@ -417,14 +418,14 @@ abstract class BindingGraph { } /** - * Returns {@code true} if {@code provisionBinding}, which was previously resolved by a parent + * Returns {@code true} if {@code binding}, which was previously resolved by a parent * resolver, should be moved into this resolver's bindings for {@code request} because it is * unscoped and {@linkplain #dependsOnLocalMultibindings(ResolvedBindings) depends on local * multibindings}, or {@code false} if it can satisfy {@code request} as an inherited binding. */ private boolean shouldOwnParentBinding( - DependencyRequest request, ProvisionBinding provisionBinding) { - return !isScoped(provisionBinding) + DependencyRequest request, ContributionBinding binding) { + return !binding.scope().isPresent() && dependsOnLocalMultibindings( getPreviouslyResolvedBindings(request.bindingKey()).get()); } @@ -446,9 +447,9 @@ abstract class BindingGraph { return membersInjectionBinding; } - private Optional<Resolver> getOwningResolver(ProvisionBinding provisionBinding) { + private Optional<Resolver> getOwningResolver(ContributionBinding provisionBinding) { for (Resolver requestResolver : getResolverLineage().reverse()) { - if (requestResolver.explicitProvisionBindingsSet.contains(provisionBinding)) { + if (requestResolver.explicitBindingsSet.contains(provisionBinding)) { return Optional.of(requestResolver); } } @@ -477,18 +478,10 @@ abstract class BindingGraph { return ImmutableList.copyOf(Lists.reverse(resolverList)); } - private ImmutableSet<ProvisionBinding> getExplicitProvisionBindings(Key requestKey) { - ImmutableSet.Builder<ProvisionBinding> explicitBindingsForKey = ImmutableSet.builder(); + private ImmutableSet<ContributionBinding> getExplicitBindings(Key requestKey) { + ImmutableSet.Builder<ContributionBinding> explicitBindingsForKey = ImmutableSet.builder(); for (Resolver resolver : getResolverLineage()) { - explicitBindingsForKey.addAll(resolver.explicitProvisionBindings.get(requestKey)); - } - return explicitBindingsForKey.build(); - } - - private ImmutableSet<ProductionBinding> getExplicitProductionBindings(Key requestKey) { - ImmutableSet.Builder<ProductionBinding> explicitBindingsForKey = ImmutableSet.builder(); - for (Resolver resolver : getResolverLineage()) { - explicitBindingsForKey.addAll(resolver.explicitProductionBindings.get(requestKey)); + explicitBindingsForKey.addAll(resolver.explicitBindings.get(requestKey)); } return explicitBindingsForKey.build(); } @@ -575,7 +568,8 @@ abstract class BindingGraph { } for (Binding binding : previouslyResolvedBindings.bindings()) { - if (!isScoped(binding) && !(binding instanceof ProductionBinding)) { + if (!binding.scope().isPresent() + && !binding.bindingType().equals(Type.PRODUCTION)) { for (DependencyRequest dependency : binding.implicitDependencies()) { if (dependsOnLocalMultibindings( getPreviouslyResolvedBindings(dependency.bindingKey()).get(), @@ -594,16 +588,7 @@ abstract class BindingGraph { } private boolean hasLocalContributions(ResolvedBindings resolvedBindings) { - return !explicitProvisionBindings.get(resolvedBindings.bindingKey().key()).isEmpty() - || !explicitProductionBindings.get(resolvedBindings.bindingKey().key()).isEmpty(); - } - - private boolean isScoped(Binding binding) { - if (binding instanceof ProvisionBinding) { - ProvisionBinding provisionBinding = (ProvisionBinding) binding; - return provisionBinding.scope().isPresent(); - } - return false; + return !explicitBindings.get(resolvedBindings.bindingKey().key()).isEmpty(); } ImmutableMap<BindingKey, ResolvedBindings> getResolvedBindings() { diff --git a/compiler/src/main/java/dagger/internal/codegen/BindingGraphValidator.java b/compiler/src/main/java/dagger/internal/codegen/BindingGraphValidator.java index 3af59d61b..f8010c301 100644 --- a/compiler/src/main/java/dagger/internal/codegen/BindingGraphValidator.java +++ b/compiler/src/main/java/dagger/internal/codegen/BindingGraphValidator.java @@ -40,7 +40,7 @@ import dagger.Lazy; import dagger.MapKey; import dagger.internal.codegen.ComponentDescriptor.BuilderSpec; import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor; -import dagger.internal.codegen.ContributionBinding.BindingType; +import dagger.internal.codegen.ContributionBinding.ContributionType; import dagger.internal.codegen.writer.TypeNames; import java.util.ArrayDeque; import java.util.Arrays; @@ -59,6 +59,7 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleTypeVisitor6; @@ -67,26 +68,35 @@ import javax.tools.Diagnostic; import static com.google.auto.common.MoreElements.getAnnotationMirror; import static com.google.auto.common.MoreTypes.asDeclared; +import static com.google.auto.common.MoreTypes.asExecutable; +import static com.google.auto.common.MoreTypes.asTypeElements; import static com.google.common.base.Predicates.equalTo; +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.all; +import static com.google.common.collect.Iterables.any; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Iterables.indexOf; import static com.google.common.collect.Iterables.skip; +import static com.google.common.collect.Maps.filterKeys; +import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor.isOfKind; +import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodKind.SUBCOMPONENT; import static dagger.internal.codegen.ConfigurationAnnotations.getComponentDependencies; import static dagger.internal.codegen.ContributionBinding.indexMapBindingsByAnnotationType; import static dagger.internal.codegen.ContributionBinding.indexMapBindingsByMapKey; import static dagger.internal.codegen.ErrorMessages.DUPLICATE_SIZE_LIMIT; import static dagger.internal.codegen.ErrorMessages.INDENT; import static dagger.internal.codegen.ErrorMessages.MEMBERS_INJECTION_WITH_UNBOUNDED_TYPE; -import static dagger.internal.codegen.ErrorMessages.NULLABLE_TO_NON_NULLABLE; import static dagger.internal.codegen.ErrorMessages.REQUIRES_AT_INJECT_CONSTRUCTOR_OR_PROVIDER_FORMAT; import static dagger.internal.codegen.ErrorMessages.REQUIRES_AT_INJECT_CONSTRUCTOR_OR_PROVIDER_OR_PRODUCER_FORMAT; import static dagger.internal.codegen.ErrorMessages.REQUIRES_PROVIDER_FORMAT; import static dagger.internal.codegen.ErrorMessages.REQUIRES_PROVIDER_OR_PRODUCER_FORMAT; import static dagger.internal.codegen.ErrorMessages.duplicateMapKeysError; import static dagger.internal.codegen.ErrorMessages.inconsistentMapKeyAnnotationsError; +import static dagger.internal.codegen.ErrorMessages.nullableToNonNullable; import static dagger.internal.codegen.ErrorMessages.stripCommonTypePrefixes; +import static dagger.internal.codegen.Util.componentCanMakeNewInstances; import static dagger.internal.codegen.Util.getKeyTypeOfMap; import static dagger.internal.codegen.Util.getProvidedValueTypeOfMap; import static dagger.internal.codegen.Util.getValueTypeOfMap; @@ -101,8 +111,7 @@ public class BindingGraphValidator { private final InjectBindingRegistry injectBindingRegistry; private final ValidationType scopeCycleValidationType; private final Diagnostic.Kind nullableValidationType; - private final ProvisionBindingFormatter provisionBindingFormatter; - private final ProductionBindingFormatter productionBindingFormatter; + private final ContributionBindingFormatter contributionBindingFormatter; private final MethodSignatureFormatter methodSignatureFormatter; private final DependencyRequestFormatter dependencyRequestFormatter; private final KeyFormatter keyFormatter; @@ -112,8 +121,7 @@ public class BindingGraphValidator { InjectBindingRegistry injectBindingRegistry, ValidationType scopeCycleValidationType, Diagnostic.Kind nullableValidationType, - ProvisionBindingFormatter provisionBindingFormatter, - ProductionBindingFormatter productionBindingFormatter, + ContributionBindingFormatter contributionBindingFormatter, MethodSignatureFormatter methodSignatureFormatter, DependencyRequestFormatter dependencyRequestFormatter, KeyFormatter keyFormatter) { @@ -121,8 +129,7 @@ public class BindingGraphValidator { this.injectBindingRegistry = injectBindingRegistry; this.scopeCycleValidationType = scopeCycleValidationType; this.nullableValidationType = nullableValidationType; - this.provisionBindingFormatter = provisionBindingFormatter; - this.productionBindingFormatter = productionBindingFormatter; + this.contributionBindingFormatter = contributionBindingFormatter; this.methodSignatureFormatter = methodSignatureFormatter; this.dependencyRequestFormatter = dependencyRequestFormatter; this.keyFormatter = keyFormatter; @@ -166,6 +173,13 @@ public class BindingGraphValidator { new HashSet<DependencyRequest>()); } } + + for (Map.Entry<ComponentMethodDescriptor, ComponentDescriptor> entry : + filterKeys(subject.componentDescriptor().subcomponents(), isOfKind(SUBCOMPONENT)) + .entrySet()) { + validateSubcomponentFactoryMethod( + entry.getKey().methodElement(), entry.getValue().componentDefinitionType()); + } for (BindingGraph subgraph : subject.subgraphs().values()) { Validation subgraphValidation = @@ -175,6 +189,39 @@ public class BindingGraphValidator { } } + private void validateSubcomponentFactoryMethod( + ExecutableElement factoryMethod, TypeElement subcomponentType) { + BindingGraph subgraph = subject.subgraphs().get(factoryMethod); + FluentIterable<TypeElement> missingModules = + FluentIterable.from(subgraph.componentRequirements()) + .filter(not(in(subgraphFactoryMethodParameters(factoryMethod)))) + .filter( + new Predicate<TypeElement>() { + @Override + public boolean apply(TypeElement moduleType) { + return !componentCanMakeNewInstances(moduleType); + } + }); + if (!missingModules.isEmpty()) { + reportBuilder.addError( + String.format( + "%s requires modules which have no visible default constructors. " + + "Add the following modules as parameters to this method: %s", + subcomponentType.getQualifiedName(), + Joiner.on(", ").join(missingModules.toSet())), + factoryMethod); + } + } + + private ImmutableSet<TypeElement> subgraphFactoryMethodParameters( + ExecutableElement factoryMethod) { + DeclaredType componentType = + asDeclared(subject.componentDescriptor().componentDefinitionType().asType()); + ExecutableType factoryMethodType = + asExecutable(types.asMemberOf(componentType, factoryMethod)); + return asTypeElements(factoryMethodType.getParameterTypes()); + } + /** * Traverse the resolved dependency requests, validating resolved bindings, and reporting any * cycles found. @@ -235,59 +282,39 @@ public class BindingGraphValidator { return false; } - ImmutableSet.Builder<ProvisionBinding> provisionBindingsBuilder = - ImmutableSet.builder(); - ImmutableSet.Builder<ProductionBinding> productionBindingsBuilder = - ImmutableSet.builder(); - ImmutableSet.Builder<MembersInjectionBinding> membersInjectionBindingsBuilder = - ImmutableSet.builder(); - for (Binding binding : resolvedBinding.bindings()) { - if (binding instanceof ProvisionBinding) { - provisionBindingsBuilder.add((ProvisionBinding) binding); - } - if (binding instanceof ProductionBinding) { - productionBindingsBuilder.add((ProductionBinding) binding); - } - if (binding instanceof MembersInjectionBinding) { - membersInjectionBindingsBuilder.add((MembersInjectionBinding) binding); - } - } - ImmutableSet<ProvisionBinding> provisionBindings = provisionBindingsBuilder.build(); - ImmutableSet<ProductionBinding> productionBindings = productionBindingsBuilder.build(); - ImmutableSet<MembersInjectionBinding> membersInjectionBindings = - membersInjectionBindingsBuilder.build(); - switch (resolvedBinding.bindingKey().kind()) { case CONTRIBUTION: - if (!membersInjectionBindings.isEmpty()) { + ImmutableSet<ContributionBinding> contributionBindings = + resolvedBinding.contributionBindings(); + if (any(contributionBindings, Binding.Type.MEMBERS_INJECTION)) { throw new IllegalArgumentException( "contribution binding keys should never have members injection bindings"); } - Set<ContributionBinding> combined = Sets.union(provisionBindings, productionBindings); - if (!validateNullability(path.peek().request(), combined)) { + if (!validateNullability(path.peek().request(), contributionBindings)) { return false; } - if (!productionBindings.isEmpty() && doesPathRequireProvisionOnly(path)) { + if (any(contributionBindings, Binding.Type.PRODUCTION) + && doesPathRequireProvisionOnly(path)) { reportProviderMayNotDependOnProducer(path); return false; } - if (combined.size() <= 1) { + if (contributionBindings.size() <= 1) { return true; } - ImmutableListMultimap<BindingType, ContributionBinding> bindingsByType = - ContributionBinding.bindingTypesFor(combined); - if (bindingsByType.keySet().size() > 1) { + ImmutableListMultimap<ContributionType, ContributionBinding> contributionsByType = + ContributionBinding.contributionTypesFor(contributionBindings); + if (contributionsByType.keySet().size() > 1) { reportMultipleBindingTypes(path); return false; } - switch (getOnlyElement(bindingsByType.keySet())) { + switch (getOnlyElement(contributionsByType.keySet())) { case UNIQUE: reportDuplicateBindings(path); return false; case MAP: - boolean duplicateMapKeys = hasDuplicateMapKeys(path, combined); + boolean duplicateMapKeys = hasDuplicateMapKeys(path, contributionBindings); boolean inconsistentMapKeyAnnotationTypes = - hasInconsistentMapKeyAnnotationTypes(path, combined); + hasInconsistentMapKeyAnnotationTypes(path, contributionBindings); return !duplicateMapKeys && !inconsistentMapKeyAnnotationTypes; case SET: break; @@ -296,21 +323,15 @@ public class BindingGraphValidator { } break; case MEMBERS_INJECTION: - if (!provisionBindings.isEmpty() || !productionBindings.isEmpty()) { + if (!all(resolvedBinding.bindings(), Binding.Type.MEMBERS_INJECTION)) { throw new IllegalArgumentException( "members injection binding keys should never have contribution bindings"); } - if (membersInjectionBindings.size() > 1) { + if (resolvedBinding.bindings().size() > 1) { reportDuplicateBindings(path); return false; } - if (membersInjectionBindings.size() == 1) { - MembersInjectionBinding binding = getOnlyElement(membersInjectionBindings); - if (!validateMembersInjectionBinding(binding, path)) { - return false; - } - } - break; + return validateMembersInjectionBinding(getOnlyElement(resolvedBinding.bindings()), path); default: throw new AssertionError(); } @@ -320,34 +341,27 @@ public class BindingGraphValidator { /** Ensures that if the request isn't nullable, then each contribution is also not nullable. */ private boolean validateNullability( DependencyRequest request, Set<ContributionBinding> bindings) { + if (request.isNullable()) { + return true; + } + + // Note: the method signature will include the @Nullable in it! + /* TODO(sameb): Sometimes javac doesn't include the Element in its output. + * (Maybe this happens if the code was already compiled before this point?) + * ... we manually print out the request in that case, otherwise the error + * message is kind of useless. */ + String typeName = TypeNames.forTypeMirror(request.key().type()).toString(); + boolean valid = true; - if (!request.isNullable()) { - String typeName = null; - for (ContributionBinding binding : bindings) { - if (binding.nullableType().isPresent()) { - String methodSignature; - if (binding instanceof ProvisionBinding) { - ProvisionBinding provisionBinding = (ProvisionBinding) binding; - methodSignature = provisionBindingFormatter.format(provisionBinding); - } else { - ProductionBinding productionBinding = (ProductionBinding) binding; - methodSignature = productionBindingFormatter.format(productionBinding); - } - // Note: the method signature will include the @Nullable in it! - // TODO(sameb): Sometimes javac doesn't include the Element in its output. - // (Maybe this happens if the code was already compiled before this point?) - // ... we manually print ouf the request in that case, otherwise the error - // message is kind of useless. - if (typeName == null) { - typeName = TypeNames.forTypeMirror(request.key().type()).toString(); - } - reportBuilder.addItem( - String.format(NULLABLE_TO_NON_NULLABLE, typeName, methodSignature) - + "\n at: " + dependencyRequestFormatter.format(request), - nullableValidationType, - request.requestElement()); - valid = false; - } + for (ContributionBinding binding : bindings) { + if (binding.nullableType().isPresent()) { + reportBuilder.addItem( + nullableToNonNullable(typeName, contributionBindingFormatter.format(binding)) + + "\n at: " + + dependencyRequestFormatter.format(request), + nullableValidationType, + request.requestElement()); + valid = false; } } return valid; @@ -375,9 +389,9 @@ public class BindingGraphValidator { * {@link MapKey} annotation type. */ private boolean hasInconsistentMapKeyAnnotationTypes( - Deque<ResolvedRequest> path, Set<ContributionBinding> mapBindings) { + Deque<ResolvedRequest> path, Set<ContributionBinding> contributionBindings) { ImmutableSetMultimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding> - mapBindingsByAnnotationType = indexMapBindingsByAnnotationType(mapBindings); + mapBindingsByAnnotationType = indexMapBindingsByAnnotationType(contributionBindings); if (mapBindingsByAnnotationType.keySet().size() > 1) { reportInconsistentMapKeyAnnotations(path, mapBindingsByAnnotationType); return true; @@ -390,7 +404,7 @@ public class BindingGraphValidator { * valid. */ private boolean validateMembersInjectionBinding( - MembersInjectionBinding binding, final Deque<ResolvedRequest> path) { + Binding binding, final Deque<ResolvedRequest> path) { return binding .key() .type() @@ -705,28 +719,27 @@ public class BindingGraphValidator { for (ResolvedBindings bindings : resolvedBindings.values()) { if (bindings.bindingKey().kind().equals(BindingKey.Kind.CONTRIBUTION)) { for (ContributionBinding contributionBinding : bindings.ownedContributionBindings()) { - if (contributionBinding instanceof ProvisionBinding) { - ProvisionBinding provisionBinding = (ProvisionBinding) contributionBinding; - Scope bindingScope = provisionBinding.scope(); - if (bindingScope.isPresent() && !componentScope.equals(bindingScope)) { - // Scoped components cannot reference bindings to @Provides methods or @Inject - // types decorated by a different scope annotation. Unscoped components cannot - // reference to scoped @Provides methods or @Inject types decorated by any - // scope annotation. - switch (provisionBinding.bindingKind()) { - case PROVISION: - ExecutableElement provisionMethod = - MoreElements.asExecutable(provisionBinding.bindingElement()); - incompatiblyScopedMethodsBuilder.add( - methodSignatureFormatter.format(provisionMethod)); - break; - case INJECTION: - incompatiblyScopedMethodsBuilder.add(bindingScope.getReadableSource() - + " class " + provisionBinding.bindingTypeElement().getQualifiedName()); - break; - default: - throw new IllegalStateException(); - } + Scope bindingScope = contributionBinding.scope(); + if (bindingScope.isPresent() && !bindingScope.equals(componentScope)) { + // Scoped components cannot reference bindings to @Provides methods or @Inject + // types decorated by a different scope annotation. Unscoped components cannot + // reference to scoped @Provides methods or @Inject types decorated by any + // scope annotation. + switch (contributionBinding.bindingKind()) { + case PROVISION: + ExecutableElement provisionMethod = + MoreElements.asExecutable(contributionBinding.bindingElement()); + incompatiblyScopedMethodsBuilder.add( + methodSignatureFormatter.format(provisionMethod)); + break; + case INJECTION: + incompatiblyScopedMethodsBuilder.add( + bindingScope.getReadableSource() + + " class " + + contributionBinding.bindingTypeElement().getQualifiedName()); + break; + default: + throw new IllegalStateException(); } } } @@ -760,7 +773,7 @@ public class BindingGraphValidator { ErrorMessages.PROVIDER_ENTRY_POINT_MAY_NOT_DEPEND_ON_PRODUCER_FORMAT, formatRootRequestKey(path)); } else { - ImmutableSet<ProvisionBinding> dependentProvisions = + ImmutableSet<? extends Binding> 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? @@ -773,8 +786,6 @@ public class BindingGraphValidator { private void reportMissingBinding(Deque<ResolvedRequest> path) { Key key = path.peek().request().key(); BindingKey bindingKey = path.peek().request().bindingKey(); - TypeMirror type = key.type(); - String typeName = TypeNames.forTypeMirror(type).toString(); boolean requiresContributionMethod = !key.isValidImplicitProvisionKey(types); boolean requiresProvision = doesPathRequireProvisionOnly(path); StringBuilder errorMessage = new StringBuilder(); @@ -785,7 +796,7 @@ public class BindingGraphValidator { : requiresProvision ? REQUIRES_AT_INJECT_CONSTRUCTOR_OR_PROVIDER_FORMAT : REQUIRES_AT_INJECT_CONSTRUCTOR_OR_PROVIDER_OR_PRODUCER_FORMAT; - errorMessage.append(String.format(requiresErrorMessageFormat, typeName)); + errorMessage.append(String.format(requiresErrorMessageFormat, keyFormatter.format(key))); if (key.isValidMembersInjectionKey() && !injectBindingRegistry.getOrFindMembersInjectionBinding(key).injectionSites() .isEmpty()) { @@ -814,10 +825,12 @@ public class BindingGraphValidator { StringBuilder builder = new StringBuilder(); new Formatter(builder) .format(ErrorMessages.DUPLICATE_BINDINGS_FOR_KEY_FORMAT, formatRootRequestKey(path)); - for (Binding binding : Iterables.limit(resolvedBinding.bindings(), DUPLICATE_SIZE_LIMIT)) { - builder.append('\n').append(INDENT).append(formatBinding(binding)); + for (ContributionBinding binding : + Iterables.limit(resolvedBinding.contributionBindings(), DUPLICATE_SIZE_LIMIT)) { + builder.append('\n').append(INDENT).append(contributionBindingFormatter.format(binding)); } - int numberOfOtherBindings = resolvedBinding.bindings().size() - DUPLICATE_SIZE_LIMIT; + int numberOfOtherBindings = + resolvedBinding.contributionBindings().size() - DUPLICATE_SIZE_LIMIT; if (numberOfOtherBindings > 0) { builder.append('\n').append(INDENT) .append("and ").append(numberOfOtherBindings).append(" other"); @@ -834,28 +847,26 @@ public class BindingGraphValidator { StringBuilder builder = new StringBuilder(); new Formatter(builder) .format(ErrorMessages.MULTIPLE_BINDING_TYPES_FOR_KEY_FORMAT, formatRootRequestKey(path)); - ImmutableListMultimap<BindingType, ContributionBinding> bindingsByType = - ContributionBinding.<ContributionBinding>bindingTypesFor( - resolvedBinding.contributionBindings()); - for (BindingType type : Ordering.natural().immutableSortedCopy(bindingsByType.keySet())) { + ImmutableListMultimap<ContributionType, ContributionBinding> bindingsByType = + ContributionBinding.contributionTypesFor(resolvedBinding.contributionBindings()); + for (ContributionType type : + Ordering.natural().immutableSortedCopy(bindingsByType.keySet())) { builder.append(INDENT); builder.append(formatBindingType(type)); builder.append(" bindings:\n"); for (ContributionBinding binding : bindingsByType.get(type)) { - builder.append(INDENT).append(INDENT); - if (binding instanceof ProvisionBinding) { - builder.append(provisionBindingFormatter.format((ProvisionBinding) binding)); - } else if (binding instanceof ProductionBinding) { - builder.append(productionBindingFormatter.format((ProductionBinding) binding)); - } - builder.append('\n'); + builder + .append(INDENT) + .append(INDENT) + .append(contributionBindingFormatter.format(binding)) + .append('\n'); } } reportBuilder.addError(builder.toString(), path.getLast().request().requestElement()); } private void reportDuplicateMapKeys( - Deque<ResolvedRequest> path, Collection<? extends ContributionBinding> mapBindings) { + Deque<ResolvedRequest> path, Collection<ContributionBinding> mapBindings) { StringBuilder builder = new StringBuilder(); builder.append(duplicateMapKeysError(formatRootRequestKey(path))); appendBindings(builder, mapBindings, 1); @@ -1061,33 +1072,32 @@ public class BindingGraphValidator { } // otherwise, the second-most-recent bindings determine whether the most recent one must be a // provision - ImmutableSet<ProvisionBinding> dependentProvisions = provisionsDependingOnLatestRequest(path); - return !dependentProvisions.isEmpty(); + return !provisionsDependingOnLatestRequest(path).isEmpty(); } /** * 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<ProvisionBinding> provisionsDependingOnLatestRequest( + private ImmutableSet<? extends Binding> provisionsDependingOnLatestRequest( Deque<ResolvedRequest> path) { Iterator<ResolvedRequest> iterator = path.iterator(); final DependencyRequest request = iterator.next().request(); ResolvedRequest previousResolvedRequest = iterator.next(); - @SuppressWarnings("unchecked") // validated by instanceof below - ImmutableSet<ProvisionBinding> bindings = (ImmutableSet<ProvisionBinding>) FluentIterable - .from(previousResolvedRequest.binding().bindings()) - .filter(new Predicate<Binding>() { - @Override public boolean apply(Binding binding) { - return binding instanceof ProvisionBinding - && binding.implicitDependencies().contains(request); - } - }).toSet(); - return bindings; + return FluentIterable.from(previousResolvedRequest.binding().bindings()) + .filter(Binding.Type.PROVISION) + .filter( + new Predicate<Binding>() { + @Override + public boolean apply(Binding binding) { + return binding.implicitDependencies().contains(request); + } + }) + .toSet(); } - private String formatBindingType(BindingType type) { - switch(type) { + private String formatBindingType(ContributionType type) { + switch (type) { case MAP: return "Map"; case SET: @@ -1099,30 +1109,18 @@ public class BindingGraphValidator { } } - private String formatBinding(Binding binding) { - // TODO(beder): Refactor the formatters so we don't need these instanceof checks. - if (binding instanceof ProvisionBinding) { - return provisionBindingFormatter.format((ProvisionBinding) binding); - } else if (binding instanceof ProductionBinding) { - return productionBindingFormatter.format((ProductionBinding) binding); - } else { - throw new IllegalArgumentException( - "Expected either a ProvisionBinding or a ProductionBinding, not " + binding); - } - } - private String formatRootRequestKey(Deque<ResolvedRequest> path) { return keyFormatter.format(path.peek().request().key()); } private void appendBindings( - StringBuilder builder, Collection<? extends Binding> bindings, int indentLevel) { - for (Binding binding : Iterables.limit(bindings, DUPLICATE_SIZE_LIMIT)) { + StringBuilder builder, Collection<ContributionBinding> bindings, int indentLevel) { + for (ContributionBinding binding : Iterables.limit(bindings, DUPLICATE_SIZE_LIMIT)) { builder.append('\n'); for (int i = 0; i < indentLevel; i++) { builder.append(INDENT); } - builder.append(formatBinding(binding)); + builder.append(contributionBindingFormatter.format(binding)); } int numberOfOtherBindings = bindings.size() - DUPLICATE_SIZE_LIMIT; if (numberOfOtherBindings > 0) { @@ -1145,12 +1143,10 @@ public class BindingGraphValidator { static ResolvedRequest create(DependencyRequest request, BindingGraph graph) { BindingKey bindingKey = request.bindingKey(); ResolvedBindings resolvedBindings = graph.resolvedBindings().get(bindingKey); - return new AutoValue_BindingGraphValidator_ResolvedRequest(request, + return new AutoValue_BindingGraphValidator_ResolvedRequest( + request, resolvedBindings == null - ? ResolvedBindings.create(bindingKey, - graph.componentDescriptor(), - ImmutableSet.<Binding>of(), - ImmutableSetMultimap.<ComponentDescriptor, Binding>of()) + ? ResolvedBindings.noBindings(bindingKey, graph.componentDescriptor()) : resolvedBindings); } } @@ -1162,4 +1158,3 @@ public class BindingGraphValidator { } }; } - diff --git a/compiler/src/main/java/dagger/internal/codegen/ComponentDescriptor.java b/compiler/src/main/java/dagger/internal/codegen/ComponentDescriptor.java index 7a5c13520..650fb9dcc 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ComponentDescriptor.java +++ b/compiler/src/main/java/dagger/internal/codegen/ComponentDescriptor.java @@ -19,6 +19,7 @@ import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; 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.ImmutableMap; @@ -73,7 +74,7 @@ abstract class ComponentDescriptor { enum Kind { COMPONENT(Component.class, Component.Builder.class, true), SUBCOMPONENT(Subcomponent.class, Subcomponent.Builder.class, false), - PRODUCTION_COMPONENT(ProductionComponent.class, null, true); + PRODUCTION_COMPONENT(ProductionComponent.class, ProductionComponent.Builder.class, true); private final Class<? extends Annotation> annotationType; private final Class<? extends Annotation> builderType; @@ -203,6 +204,18 @@ abstract class ComponentDescriptor { abstract ComponentMethodKind kind(); abstract Optional<DependencyRequest> dependencyRequest(); abstract ExecutableElement methodElement(); + + /** + * A predicate that passes for {@link ComponentMethodDescriptor}s of a given kind. + */ + static Predicate<ComponentMethodDescriptor> isOfKind(final ComponentMethodKind kind) { + return new Predicate<ComponentMethodDescriptor>() { + @Override + public boolean apply(ComponentMethodDescriptor descriptor) { + return kind.equals(descriptor.kind()); + } + }; + } } enum ComponentMethodKind { @@ -212,7 +225,7 @@ abstract class ComponentDescriptor { SUBCOMPONENT, SUBCOMPONENT_BUILDER, } - + @AutoValue static abstract class BuilderSpec { abstract TypeElement builderDefinitionType(); @@ -284,6 +297,9 @@ abstract class ComponentDescriptor { for (TypeMirror moduleIncludesType : getComponentModules(componentMirror)) { modules.add(moduleDescriptorFactory.create(MoreTypes.asTypeElement(moduleIncludesType))); } + if (kind.equals(Kind.PRODUCTION_COMPONENT)) { + modules.add(descriptorForMonitoringModule(componentDefinitionType)); + } ImmutableSet<ExecutableElement> unimplementedMethods = Util.getUnimplementedMethods(elements, componentDefinitionType); @@ -434,6 +450,23 @@ abstract class ComponentDescriptor { return Optional.<BuilderSpec>of(new AutoValue_ComponentDescriptor_BuilderSpec(element, map.build(), buildMethod, element.getEnclosingElement().asType())); } + + /** + * Returns a descriptor for a generated module that handles monitoring for production + * components. This module is generated in the {@link MonitoringModuleProcessingStep}. + * + * @throws TypeNotPresentException if the module has not been generated yet. This will cause the + * processor to retry in a later processing round. + */ + private ModuleDescriptor descriptorForMonitoringModule(TypeElement componentDefinitionType) { + String generatedMonitorModuleName = + SourceFiles.generatedMonitoringModuleName(componentDefinitionType).canonicalName(); + TypeElement monitoringModule = elements.getTypeElement(generatedMonitorModuleName); + if (monitoringModule == null) { + throw new TypeNotPresentException(generatedMonitorModuleName, null); + } + return moduleDescriptorFactory.create(monitoringModule); + } } static boolean isComponentContributionMethod(Elements elements, ExecutableElement method) { diff --git a/compiler/src/main/java/dagger/internal/codegen/ComponentGenerator.java b/compiler/src/main/java/dagger/internal/codegen/ComponentGenerator.java index bb75e14bd..72e761cbf 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ComponentGenerator.java +++ b/compiler/src/main/java/dagger/internal/codegen/ComponentGenerator.java @@ -74,16 +74,6 @@ final class ComponentGenerator extends SourceFileGenerator<BindingGraph> { return Optional.of(input.componentDescriptor().componentDefinitionType()); } - @AutoValue - static abstract class ProxyClassAndField { - abstract ClassWriter proxyWriter(); - abstract FieldWriter proxyFieldWriter(); - - static ProxyClassAndField create(ClassWriter proxyWriter, FieldWriter proxyFieldWriter) { - return new AutoValue_ComponentGenerator_ProxyClassAndField(proxyWriter, proxyFieldWriter); - } - } - @AutoValue static abstract class MemberSelect { static MemberSelect instanceSelect(ClassName owningClass, Snippet snippet) { return new AutoValue_ComponentGenerator_MemberSelect( diff --git a/compiler/src/main/java/dagger/internal/codegen/ComponentProcessor.java b/compiler/src/main/java/dagger/internal/codegen/ComponentProcessor.java index 1e96a534a..c35058ba3 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ComponentProcessor.java +++ b/compiler/src/main/java/dagger/internal/codegen/ComponentProcessor.java @@ -80,10 +80,8 @@ public final class ComponentProcessor extends BasicAnnotationProcessor { nullableValidationType(processingEnv).diagnosticKind().get(); MethodSignatureFormatter methodSignatureFormatter = new MethodSignatureFormatter(types); - ProvisionBindingFormatter provisionBindingFormatter = - new ProvisionBindingFormatter(methodSignatureFormatter); - ProductionBindingFormatter productionBindingFormatter = - new ProductionBindingFormatter(methodSignatureFormatter); + ContributionBindingFormatter contributionBindingFormatter = + new ContributionBindingFormatter(methodSignatureFormatter); DependencyRequestFormatter dependencyRequestFormatter = new DependencyRequestFormatter(types); KeyFormatter keyFormatter = new KeyFormatter(); @@ -122,6 +120,8 @@ public final class ComponentProcessor extends BasicAnnotationProcessor { Produces.class); ProducesMethodValidator producesMethodValidator = new ProducesMethodValidator(elements); ProductionComponentValidator productionComponentValidator = new ProductionComponentValidator(); + BuilderValidator productionComponentBuilderValidator = + new BuilderValidator(elements, types, ComponentDescriptor.Kind.PRODUCTION_COMPONENT); Key.Factory keyFactory = new Key.Factory(types, elements); @@ -133,8 +133,10 @@ public final class ComponentProcessor extends BasicAnnotationProcessor { new ComponentGenerator(filer, elements, types, keyFactory, nullableDiagnosticType); ProducerFactoryGenerator producerFactoryGenerator = new ProducerFactoryGenerator(filer, DependencyRequestMapper.FOR_PRODUCER); + MonitoringModuleGenerator monitoringModuleGenerator = new MonitoringModuleGenerator(filer); - DependencyRequest.Factory dependencyRequestFactory = new DependencyRequest.Factory(keyFactory); + DependencyRequest.Factory dependencyRequestFactory = + new DependencyRequest.Factory(elements, keyFactory); ProvisionBinding.Factory provisionBindingFactory = new ProvisionBinding.Factory(elements, types, keyFactory, dependencyRequestFactory); ProductionBinding.Factory productionBindingFactory = @@ -152,13 +154,13 @@ public final class ComponentProcessor extends BasicAnnotationProcessor { ComponentDescriptor.Factory componentDescriptorFactory = new ComponentDescriptor.Factory( elements, types, dependencyRequestFactory, moduleDescriptorFactory); - BindingGraph.Factory bindingGraphFactory = new BindingGraph.Factory( - elements, - injectBindingRegistry, - keyFactory, - dependencyRequestFactory, - provisionBindingFactory, - productionBindingFactory); + BindingGraph.Factory bindingGraphFactory = + new BindingGraph.Factory( + elements, + injectBindingRegistry, + keyFactory, + provisionBindingFactory, + productionBindingFactory); MapKeyGenerator mapKeyGenerator = new MapKeyGenerator(filer); ComponentHierarchyValidator componentHierarchyValidator = new ComponentHierarchyValidator(); @@ -168,8 +170,7 @@ public final class ComponentProcessor extends BasicAnnotationProcessor { injectBindingRegistry, scopeValidationType(processingEnv), nullableDiagnosticType, - provisionBindingFormatter, - productionBindingFormatter, + contributionBindingFormatter, methodSignatureFormatter, dependencyRequestFormatter, keyFormatter); @@ -184,6 +185,7 @@ public final class ComponentProcessor extends BasicAnnotationProcessor { provisionBindingFactory, membersInjectionBindingFactory, injectBindingRegistry), + new MonitoringModuleProcessingStep(messager, monitoringModuleGenerator), new ModuleProcessingStep( messager, moduleValidator, @@ -210,6 +212,7 @@ public final class ComponentProcessor extends BasicAnnotationProcessor { new ProductionComponentProcessingStep( messager, productionComponentValidator, + productionComponentBuilderValidator, componentHierarchyValidator, bindingGraphValidator, componentDescriptorFactory, diff --git a/compiler/src/main/java/dagger/internal/codegen/ComponentValidator.java b/compiler/src/main/java/dagger/internal/codegen/ComponentValidator.java index 58fe1ca0c..a9e82a8cf 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ComponentValidator.java +++ b/compiler/src/main/java/dagger/internal/codegen/ComponentValidator.java @@ -18,17 +18,13 @@ package dagger.internal.codegen; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; -import com.google.common.base.Joiner; 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 com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; -import com.google.common.collect.Sets.SetView; import dagger.Component; import dagger.Module; import dagger.Subcomponent; @@ -54,7 +50,6 @@ import static com.google.auto.common.MoreElements.getAnnotationMirror; import static dagger.internal.codegen.ConfigurationAnnotations.enclosedBuilders; import static dagger.internal.codegen.ConfigurationAnnotations.getComponentModules; import static dagger.internal.codegen.ConfigurationAnnotations.getTransitiveModules; -import static dagger.internal.codegen.Util.componentCanMakeNewInstances; import static javax.lang.model.element.ElementKind.CLASS; import static javax.lang.model.element.ElementKind.INTERFACE; import static javax.lang.model.element.Modifier.ABSTRACT; @@ -264,18 +259,10 @@ final class ComponentValidator { // TODO(gak): This logic maybe/probably shouldn't live here as it requires us to traverse // subcomponents and their modules separately from how it is done in ComponentDescriptor and // ModuleDescriptor + @SuppressWarnings("deprecation") ImmutableSet<TypeElement> transitiveModules = getTransitiveModules(types, elements, moduleTypes); - ImmutableSet<TypeElement> requiredModules = - FluentIterable.from(transitiveModules) - .filter(new Predicate<TypeElement>() { - @Override public boolean apply(TypeElement input) { - return !componentCanMakeNewInstances(input); - } - }) - .toSet(); - Set<TypeElement> variableTypes = Sets.newHashSet(); for (int i = 0; i < parameterTypes.size(); i++) { @@ -320,18 +307,6 @@ final class ComponentValidator { parameter); } } - - SetView<TypeElement> missingModules = - Sets.difference(requiredModules, ImmutableSet.copyOf(variableTypes)); - if (!missingModules.isEmpty()) { - builder.addError( - String.format( - "%s requires modules which have no visible default constructors. " - + "Add the following modules as parameters to this method: %s", - MoreTypes.asTypeElement(returnType).getQualifiedName(), - Joiner.on(", ").join(missingModules)), - method); - } } private void validateSubcomponentBuilderMethod(ValidationReport.Builder<TypeElement> builder, diff --git a/compiler/src/main/java/dagger/internal/codegen/ContributionBinding.java b/compiler/src/main/java/dagger/internal/codegen/ContributionBinding.java index a0a4dc148..831943f2a 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ContributionBinding.java +++ b/compiler/src/main/java/dagger/internal/codegen/ContributionBinding.java @@ -20,15 +20,22 @@ import com.google.common.base.Equivalence; import com.google.common.base.Equivalence.Wrapper; import com.google.common.base.Function; import com.google.common.base.Optional; +import com.google.common.base.Predicate; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Multimaps; import com.google.common.collect.Ordering; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.ListenableFuture; +import dagger.Component; import dagger.MapKey; +import dagger.Provides; +import dagger.producers.Produces; +import dagger.producers.ProductionComponent; import java.util.EnumSet; import java.util.Set; -import javax.inject.Provider; +import javax.inject.Inject; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.TypeElement; @@ -38,6 +45,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.MapKeys.getMapKey; import static dagger.internal.codegen.MapKeys.unwrapValue; +import static javax.lang.model.element.Modifier.STATIC; /** * An abstract class for a value object representing the mechanism by which a {@link Key} can be @@ -47,7 +55,19 @@ import static dagger.internal.codegen.MapKeys.unwrapValue; * @since 2.0 */ abstract class ContributionBinding extends Binding { - static enum BindingType { + + @Override + Set<DependencyRequest> implicitDependencies() { + // Optimization: If we don't need the memberInjectionRequest, don't create more objects. + if (!membersInjectionRequest().isPresent()) { + return dependencies(); + } else { + // Optimization: Avoid creating an ImmutableSet+Builder just to union two things together. + return Sets.union(membersInjectionRequest().asSet(), dependencies()); + } + } + + static enum ContributionType { /** Represents map bindings. */ MAP, /** Represents set bindings. */ @@ -60,8 +80,20 @@ abstract class ContributionBinding extends Binding { } } - abstract BindingType bindingType(); - + ContributionType contributionType() { + switch (provisionType()) { + case SET: + case SET_VALUES: + return ContributionType.SET; + case MAP: + return ContributionType.MAP; + case UNIQUE: + return ContributionType.UNIQUE; + default: + throw new AssertionError("Unknown provision type: " + provisionType()); + } + } + /** Returns the type that specifies this' nullability, absent if not nullable. */ abstract Optional<DeclaredType> nullableType(); @@ -77,46 +109,142 @@ abstract class ContributionBinding extends Binding { * Returns whether this binding is synthetic, i.e., not explicitly tied to code, but generated * implicitly by the framework. */ - // TODO(user): Remove the SYNTHETIC enums from ProvisionBinding and ProductionBinding and make - // this field the source of truth for synthetic bindings. - abstract boolean isSyntheticBinding(); + boolean isSyntheticBinding() { + return bindingKind().equals(Kind.SYNTHETIC); + } + + /** If this provision requires members injection, this will be the corresponding request. */ + abstract Optional<DependencyRequest> membersInjectionRequest(); /** - * Returns the framework class associated with this binding, e.g., {@link Provider} for a - * ProvisionBinding. + * The kind of contribution this binding represents. Defines which elements can specify this kind + * of contribution. */ - abstract Class<?> frameworkClass(); + enum Kind { + /** + * A binding that is not explicitly tied to an element, but generated implicitly by the + * framework. + */ + SYNTHETIC, + + // Provision kinds + + /** An {@link Inject}-annotated constructor. */ + INJECTION, + + /** A {@link Provides}-annotated method. */ + PROVISION, + + /** An implicit binding to a {@link Component @Component}-annotated type. */ + COMPONENT, + + /** A provision method on a component's {@linkplain Component#dependencies() dependency}. */ + COMPONENT_PROVISION, + + /** + * A subcomponent builder method on a component or subcomponent. + */ + SUBCOMPONENT_BUILDER, + + // Production kinds + + /** A {@link Produces}-annotated method that doesn't return a {@link ListenableFuture}. */ + IMMEDIATE, + + /** A {@link Produces}-annotated method that returns a {@link ListenableFuture}. */ + FUTURE_PRODUCTION, + + /** + * A production method on a production component's + * {@linkplain ProductionComponent#dependencies() dependency} that returns a + * {@link ListenableFuture}. Methods on production component dependencies that don't return a + * {@link ListenableFuture} are considered {@linkplain #PROVISION provision bindings}. + */ + COMPONENT_PRODUCTION, + } + + /** + * The kind of this contribution binding. + */ + protected abstract Kind bindingKind(); + + /** + * A predicate that passes for bindings of a given kind. + */ + static Predicate<ContributionBinding> isOfKind(final Kind kind) { + return new Predicate<ContributionBinding>() { + @Override + public boolean apply(ContributionBinding binding) { + return binding.bindingKind().equals(kind); + }}; + } + + /** The provision type that was used to bind the key. */ + abstract Provides.Type provisionType(); + + /** + * The strategy for getting an instance of a factory for a {@link ContributionBinding}. + */ + enum FactoryCreationStrategy { + /** The factory class is an enum with one value named {@code INSTANCE}. */ + ENUM_INSTANCE, + /** The factory must be created by calling the constructor. */ + CLASS_CONSTRUCTOR, + } + + /** + * Returns {@link FactoryCreationStrategy#ENUM_INSTANCE} if the binding has no dependencies and + * is a static provision binding or an {@link Inject @Inject} constructor binding. Otherwise + * returns {@link FactoryCreationStrategy#CLASS_CONSTRUCTOR}. + */ + FactoryCreationStrategy factoryCreationStrategy() { + switch (bindingKind()) { + case PROVISION: + return implicitDependencies().isEmpty() && bindingElement().getModifiers().contains(STATIC) + ? FactoryCreationStrategy.ENUM_INSTANCE + : FactoryCreationStrategy.CLASS_CONSTRUCTOR; + + case INJECTION: + return implicitDependencies().isEmpty() + ? FactoryCreationStrategy.ENUM_INSTANCE + : FactoryCreationStrategy.CLASS_CONSTRUCTOR; + + default: + return FactoryCreationStrategy.CLASS_CONSTRUCTOR; + } + } /** - * Returns the set of {@link BindingType} enum values implied by a given - * {@link ContributionBinding} collection. + * Returns the {@link ContributionType}s represented by a given {@link ContributionBinding} + * collection. */ - static <B extends ContributionBinding> ImmutableListMultimap<BindingType, B> bindingTypesFor( - Iterable<? extends B> bindings) { - ImmutableListMultimap.Builder<BindingType, B> builder = - ImmutableListMultimap.builder(); - builder.orderKeysBy(Ordering.<BindingType>natural()); + static <B extends ContributionBinding> + ImmutableListMultimap<ContributionType, B> contributionTypesFor( + Iterable<? extends B> bindings) { + ImmutableListMultimap.Builder<ContributionType, B> builder = ImmutableListMultimap.builder(); + builder.orderKeysBy(Ordering.<ContributionType>natural()); for (B binding : bindings) { - builder.put(binding.bindingType(), binding); + builder.put(binding.contributionType(), binding); } return builder.build(); } /** - * Returns a single {@code BindingsType} represented by a given collection of - * {@code ContributionBindings} or throws an IllegalArgumentException if the given bindings - * are not all of one type. + * Returns a single {@link ContributionType} represented by a given collection of + * {@link ContributionBinding}s. + * + * @throws IllegalArgumentException if the given bindings are not all of one type */ - static BindingType bindingTypeFor(Iterable<? extends ContributionBinding> bindings) { + static ContributionType contributionTypeFor(Iterable<ContributionBinding> bindings) { checkNotNull(bindings); checkArgument(!Iterables.isEmpty(bindings), "no bindings"); - Set<BindingType> types = EnumSet.noneOf(BindingType.class); + Set<ContributionType> types = EnumSet.noneOf(ContributionType.class); for (ContributionBinding binding : bindings) { - types.add(binding.bindingType()); + types.add(binding.contributionType()); } if (types.size() > 1) { throw new IllegalArgumentException( - String.format(ErrorMessages.MULTIPLE_BINDING_TYPES_FORMAT, types)); + String.format(ErrorMessages.MULTIPLE_CONTRIBUTION_TYPES_FORMAT, types)); } return Iterables.getOnlyElement(types); } diff --git a/compiler/src/main/java/dagger/internal/codegen/ProductionBindingFormatter.java b/compiler/src/main/java/dagger/internal/codegen/ContributionBindingFormatter.java index e7e7e778a..0d267619b 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ProductionBindingFormatter.java +++ b/compiler/src/main/java/dagger/internal/codegen/ContributionBindingFormatter.java @@ -21,26 +21,32 @@ import static com.google.auto.common.MoreElements.asExecutable; import static com.google.auto.common.MoreTypes.asDeclared; /** - * Formats a {@link ProductionBinding} into a {@link String} suitable for use in error messages. + * Formats a {@link ContributionBinding} into a {@link String} suitable for use in error messages. * - * @author Jesse Beder + * @author Christian Gruber * @since 2.0 */ -final class ProductionBindingFormatter extends Formatter<ProductionBinding> { +final class ContributionBindingFormatter extends Formatter<ContributionBinding> { private final MethodSignatureFormatter methodSignatureFormatter; - - ProductionBindingFormatter(MethodSignatureFormatter methodSignatureFormatter) { + + ContributionBindingFormatter(MethodSignatureFormatter methodSignatureFormatter) { this.methodSignatureFormatter = methodSignatureFormatter; } - @Override public String format(ProductionBinding binding) { + @Override public String format(ContributionBinding binding) { switch (binding.bindingKind()) { + case COMPONENT_PROVISION: + case COMPONENT_PRODUCTION: + return methodSignatureFormatter.format(asExecutable(binding.bindingElement())); + + case PROVISION: + case SUBCOMPONENT_BUILDER: case IMMEDIATE: case FUTURE_PRODUCTION: - return methodSignatureFormatter.format(asExecutable(binding.bindingElement()), + return methodSignatureFormatter.format( + asExecutable(binding.bindingElement()), Optional.of(asDeclared(binding.contributedBy().get().asType()))); - case COMPONENT_PRODUCTION: - return methodSignatureFormatter.format(asExecutable(binding.bindingElement())); + default: throw new UnsupportedOperationException( "Not yet supporting " + binding.bindingKind() + " binding types."); diff --git a/compiler/src/main/java/dagger/internal/codegen/DependencyRequest.java b/compiler/src/main/java/dagger/internal/codegen/DependencyRequest.java index 5af9a82c5..49fcd9c9b 100644 --- a/compiler/src/main/java/dagger/internal/codegen/DependencyRequest.java +++ b/compiler/src/main/java/dagger/internal/codegen/DependencyRequest.java @@ -29,23 +29,27 @@ import dagger.MembersInjector; import dagger.Provides; import dagger.producers.Produced; import dagger.producers.Producer; +import dagger.producers.internal.AbstractProducer; 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.ExecutableType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; import static com.google.auto.common.MoreTypes.isTypeOf; 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 javax.lang.model.type.TypeKind.DECLARED; +import static javax.lang.model.util.ElementFilter.constructorsIn; /** * Represents a request for a key at an injection point. Parameters to {@link Inject} constructors @@ -57,7 +61,7 @@ import static javax.lang.model.type.TypeKind.DECLARED; // TODO(gak): Set bindings and the permutations thereof need to be addressed @AutoValue abstract class DependencyRequest { - static Function<DependencyRequest, BindingKey> BINDING_KEY_FUNCTION = + static final Function<DependencyRequest, BindingKey> BINDING_KEY_FUNCTION = new Function<DependencyRequest, BindingKey>() { @Override public BindingKey apply(DependencyRequest request) { return request.bindingKey(); @@ -115,15 +119,23 @@ abstract class DependencyRequest { abstract boolean isNullable(); /** + * An optional name for this request when it's referred to in generated code. If absent, it will + * use a name derived from {@link #requestElement}. + */ + abstract Optional<String> overriddenVariableName(); + + /** * Factory for {@link DependencyRequest}s. * * <p>Any factory method may throw {@link TypeNotPresentException} if a type is not available, * which may mean that the type will be generated in a later round of processing. */ static final class Factory { + private final Elements elements; private final Key.Factory keyFactory; - Factory(Key.Factory keyFactory) { + Factory(Elements elements, Key.Factory keyFactory) { + this.elements = elements; this.keyFactory = keyFactory; } @@ -140,33 +152,46 @@ abstract class DependencyRequest { ImmutableSet<DependencyRequest> forRequiredVariables( List<? extends VariableElement> variables) { return FluentIterable.from(variables) - .transform(new Function<VariableElement, DependencyRequest>() { - @Override public DependencyRequest apply(VariableElement input) { - return forRequiredVariable(input); - } - }) + .transform( + new Function<VariableElement, DependencyRequest>() { + @Override + public DependencyRequest apply(VariableElement input) { + return forRequiredVariable(input); + } + }) .toSet(); } /** - * Creates a DependencyRequest for implictMapBinding, this request's key will be - * {@code Map<K, Provider<V>>}, this DependencyRequest is depended by the DependencyRequest - * whose key is {@code Map<K, V>} + * Creates a implicit {@link DependencyRequest} for {@code mapOfFactoryKey}, which will be used + * to satisfy the {@code mapOfValueRequest}. + * + * @param mapOfValueRequest a request for {@code Map<K, V>} + * @param mapOfFactoryKey a key equivalent to {@code mapOfValueRequest}'s key, whose type is + * {@code Map<K, Provider<V>>} or {@code Map<K, Producer<V>>} */ - DependencyRequest forImplicitMapBinding(DependencyRequest delegatingRequest, Key delegateKey) { - checkNotNull(delegatingRequest); - return new AutoValue_DependencyRequest(Kind.PROVIDER, delegateKey, - delegatingRequest.requestElement(), - delegatingRequest.enclosingType(), - false /* doesn't allow null */); + DependencyRequest forImplicitMapBinding( + DependencyRequest mapOfValueRequest, Key mapOfFactoryKey) { + checkNotNull(mapOfValueRequest); + return new AutoValue_DependencyRequest( + Kind.PROVIDER, + mapOfFactoryKey, + mapOfValueRequest.requestElement(), + mapOfValueRequest.enclosingType(), + false /* doesn't allow null */, + Optional.<String>absent()); } DependencyRequest forRequiredVariable(VariableElement variableElement) { + return forRequiredVariable(variableElement, Optional.<String>absent()); + } + + DependencyRequest forRequiredVariable(VariableElement variableElement, Optional<String> name) { checkNotNull(variableElement); TypeMirror type = variableElement.asType(); Optional<AnnotationMirror> qualifier = InjectionAnnotations.getQualifier(variableElement); - return newDependencyRequest(variableElement, type, qualifier, - getEnclosingType(variableElement)); + return newDependencyRequest( + variableElement, type, qualifier, getEnclosingType(variableElement), name); } DependencyRequest forRequiredResolvedVariable(DeclaredType container, @@ -175,18 +200,25 @@ abstract class DependencyRequest { checkNotNull(variableElement); checkNotNull(resolvedType); Optional<AnnotationMirror> qualifier = InjectionAnnotations.getQualifier(variableElement); - return newDependencyRequest(variableElement, resolvedType, qualifier, container); + return newDependencyRequest( + variableElement, resolvedType, qualifier, container, Optional.<String>absent()); } DependencyRequest forComponentProvisionMethod(ExecutableElement provisionMethod, ExecutableType provisionMethodType) { checkNotNull(provisionMethod); checkNotNull(provisionMethodType); - checkArgument(provisionMethod.getParameters().isEmpty(), - "Component provision methods must be empty: " + provisionMethod); + checkArgument( + provisionMethod.getParameters().isEmpty(), + "Component provision methods must be empty: %s", + provisionMethod); Optional<AnnotationMirror> qualifier = InjectionAnnotations.getQualifier(provisionMethod); - return newDependencyRequest(provisionMethod, provisionMethodType.getReturnType(), qualifier, - getEnclosingType(provisionMethod)); + return newDependencyRequest( + provisionMethod, + provisionMethodType.getReturnType(), + qualifier, + getEnclosingType(provisionMethod), + Optional.<String>absent()); } DependencyRequest forComponentProductionMethod(ExecutableElement productionMethod, @@ -203,13 +235,15 @@ abstract class DependencyRequest { if (isTypeOf(ListenableFuture.class, type)) { return new AutoValue_DependencyRequest( Kind.FUTURE, - keyFactory.forQualifiedType(qualifier, - Iterables.getOnlyElement(((DeclaredType) type).getTypeArguments())), + keyFactory.forQualifiedType( + qualifier, Iterables.getOnlyElement(((DeclaredType) type).getTypeArguments())), productionMethod, container, - false /* doesn't allow null */); + false /* doesn't allow null */, + Optional.<String>absent()); } else { - return newDependencyRequest(productionMethod, type, qualifier, container); + return newDependencyRequest( + productionMethod, type, qualifier, container, Optional.<String>absent()); } } @@ -223,32 +257,53 @@ abstract class DependencyRequest { TypeMirror returnType = membersInjectionMethodType.getReturnType(); if (returnType.getKind().equals(DECLARED) && MoreTypes.isTypeOf(MembersInjector.class, returnType)) { - return new AutoValue_DependencyRequest(Kind.MEMBERS_INJECTOR, + return new AutoValue_DependencyRequest( + Kind.MEMBERS_INJECTOR, keyFactory.forMembersInjectedType( Iterables.getOnlyElement(((DeclaredType) returnType).getTypeArguments())), - membersInjectionMethod, - getEnclosingType(membersInjectionMethod), - false /* doesn't allow null */); + membersInjectionMethod, + getEnclosingType(membersInjectionMethod), + false /* doesn't allow null */, + Optional.<String>absent()); } else { - return new AutoValue_DependencyRequest(Kind.MEMBERS_INJECTOR, + return new AutoValue_DependencyRequest( + Kind.MEMBERS_INJECTOR, keyFactory.forMembersInjectedType( Iterables.getOnlyElement(membersInjectionMethodType.getParameterTypes())), - membersInjectionMethod, - getEnclosingType(membersInjectionMethod), - false /* doesn't allow null */); + membersInjectionMethod, + getEnclosingType(membersInjectionMethod), + false /* doesn't allow null */, + Optional.<String>absent()); } } DependencyRequest forMembersInjectedType(DeclaredType type) { - return new AutoValue_DependencyRequest(Kind.MEMBERS_INJECTOR, + return new AutoValue_DependencyRequest( + Kind.MEMBERS_INJECTOR, keyFactory.forMembersInjectedType(type), type.asElement(), type, - false /* doesn't allow null */); + false /* doesn't allow null */, + Optional.<String>absent()); + } + + DependencyRequest forProductionComponentMonitorProvider() { + TypeElement element = elements.getTypeElement(AbstractProducer.class.getCanonicalName()); + for (ExecutableElement constructor : constructorsIn(element.getEnclosedElements())) { + if (constructor.getParameters().size() == 2) { + // the 2-arg constructor has the appropriate dependency as its first arg + return forRequiredVariable(constructor.getParameters().get(0), Optional.of("monitor")); + } + } + throw new AssertionError("expected 2-arg constructor in AbstractProducer"); } - private DependencyRequest newDependencyRequest(Element requestElement, - TypeMirror type, Optional<AnnotationMirror> qualifier, DeclaredType container) { + private DependencyRequest newDependencyRequest( + Element requestElement, + TypeMirror type, + Optional<AnnotationMirror> qualifier, + DeclaredType container, + Optional<String> name) { KindAndType kindAndType = extractKindAndType(type); if (kindAndType.kind().equals(Kind.MEMBERS_INJECTOR)) { checkArgument(!qualifier.isPresent()); @@ -258,11 +313,13 @@ abstract class DependencyRequest { // TODO(sameb): should Produced/Producer always require non-nullable? boolean allowsNull = !kindAndType.kind().equals(Kind.INSTANCE) || ConfigurationAnnotations.getNullableType(requestElement).isPresent(); - return new AutoValue_DependencyRequest(kindAndType.kind(), + return new AutoValue_DependencyRequest( + kindAndType.kind(), keyFactory.forQualifiedType(qualifier, kindAndType.type()), requestElement, container, - allowsNull); + allowsNull, + name); } @AutoValue diff --git a/compiler/src/main/java/dagger/internal/codegen/DependencyVariableNamer.java b/compiler/src/main/java/dagger/internal/codegen/DependencyVariableNamer.java index 1643adbc4..5ba5635cd 100644 --- a/compiler/src/main/java/dagger/internal/codegen/DependencyVariableNamer.java +++ b/compiler/src/main/java/dagger/internal/codegen/DependencyVariableNamer.java @@ -33,6 +33,9 @@ import javax.inject.Provider; final class DependencyVariableNamer implements Function<DependencyRequest, String> { @Override public String apply(DependencyRequest dependency) { + if (dependency.overriddenVariableName().isPresent()) { + return dependency.overriddenVariableName().get(); + } String variableName = dependency.requestElement().getSimpleName().toString(); switch (dependency.kind()) { case INSTANCE: diff --git a/compiler/src/main/java/dagger/internal/codegen/ErrorMessages.java b/compiler/src/main/java/dagger/internal/codegen/ErrorMessages.java index c025eb48a..1f52aca98 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ErrorMessages.java +++ b/compiler/src/main/java/dagger/internal/codegen/ErrorMessages.java @@ -185,7 +185,7 @@ final class ErrorMessages { "Map key annotations with unwrapped values cannot use arrays"; /* collection binding errors */ - static final String MULTIPLE_BINDING_TYPES_FORMAT = + static final String MULTIPLE_CONTRIBUTION_TYPES_FORMAT = "More than one binding present of different types %s"; static final String MULTIPLE_BINDING_TYPES_FOR_KEY_FORMAT = @@ -224,8 +224,12 @@ final class ErrorMessages { static final String MALFORMED_MODULE_METHOD_FORMAT = "Cannot generated a graph because method %s on module %s was malformed"; - static final String NULLABLE_TO_NON_NULLABLE = - "%s is not nullable, but is being provided by %s"; + static String nullableToNonNullable(String typeName, String bindingString) { + return String.format( + "%s is not nullable, but is being provided by %s", + typeName, + bindingString); + } static final String CANNOT_RETURN_NULL_FROM_NON_NULLABLE_COMPONENT_METHOD = "Cannot return null from a non-@Nullable component method"; @@ -239,6 +243,8 @@ final class ErrorMessages { return ComponentBuilderMessages.INSTANCE; case SUBCOMPONENT: return SubcomponentBuilderMessages.INSTANCE; + case PRODUCTION_COMPONENT: + return ProductionComponentBuilderMessages.INSTANCE; default: throw new IllegalStateException(kind.toString()); } @@ -370,6 +376,17 @@ final class ErrorMessages { } } + static final class ProductionComponentBuilderMessages extends ComponentBuilderMessages { + @SuppressWarnings("hiding") + static final ProductionComponentBuilderMessages INSTANCE = + new ProductionComponentBuilderMessages(); + + @Override protected String process(String s) { + return s.replaceAll("component", "production component") + .replaceAll("Component", "ProductionComponent"); + } + } + /** * 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/FactoryGenerator.java b/compiler/src/main/java/dagger/internal/codegen/FactoryGenerator.java index c7eb6326f..a0a48c81e 100644 --- a/compiler/src/main/java/dagger/internal/codegen/FactoryGenerator.java +++ b/compiler/src/main/java/dagger/internal/codegen/FactoryGenerator.java @@ -52,11 +52,11 @@ import javax.tools.Diagnostic; import static com.google.common.base.Preconditions.checkState; import static dagger.Provides.Type.SET; +import static dagger.internal.codegen.ContributionBinding.Kind.PROVISION; import static dagger.internal.codegen.ErrorMessages.CANNOT_RETURN_NULL_FROM_NON_NULLABLE_PROVIDES_METHOD; -import static dagger.internal.codegen.ProvisionBinding.Kind.PROVISION; -import static dagger.internal.codegen.SourceFiles.factoryNameForProvisionBinding; import static dagger.internal.codegen.SourceFiles.frameworkTypeUsageStatement; -import static dagger.internal.codegen.SourceFiles.parameterizedFactoryNameForProvisionBinding; +import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding; +import static dagger.internal.codegen.SourceFiles.parameterizedGeneratedTypeNameForBinding; import static dagger.internal.codegen.writer.Snippet.makeParametersSnippet; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; @@ -83,7 +83,7 @@ final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding> { @Override ClassName nameGeneratedType(ProvisionBinding binding) { - return factoryNameForProvisionBinding(binding); + return generatedClassNameForBinding(binding); } @Override @@ -156,7 +156,7 @@ final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding> { getMethodWriter.annotate(Override.class); getMethodWriter.addModifiers(PUBLIC); - if (binding.memberInjectionRequest().isPresent()) { + if (binding.membersInjectionRequest().isPresent()) { ParameterizedTypeName membersInjectorType = ParameterizedTypeName.create( MembersInjector.class, providedTypeName); factoryWriter.addField(membersInjectorType, "membersInjector").addModifiers(PRIVATE, FINAL); @@ -211,7 +211,7 @@ final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding> { break; case CLASS_CONSTRUCTOR: createMethodWriter.body().addSnippet("return new %s(%s);", - parameterizedFactoryNameForProvisionBinding(binding), + parameterizedGeneratedTypeNameForBinding(binding), Joiner.on(", ").join(params.keySet())); break; default: @@ -262,7 +262,7 @@ final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding> { providesMethodInvocation, failMsg)); } - } else if (binding.memberInjectionRequest().isPresent()) { + } else if (binding.membersInjectionRequest().isPresent()) { getMethodWriter.body().addSnippet("%1$s instance = new %1$s(%2$s);", providedTypeName, parametersSnippet); getMethodWriter.body().addSnippet("membersInjector.injectMembers(instance);"); diff --git a/compiler/src/main/java/dagger/internal/codegen/FrameworkField.java b/compiler/src/main/java/dagger/internal/codegen/FrameworkField.java index b92cef796..38e8f026a 100644 --- a/compiler/src/main/java/dagger/internal/codegen/FrameworkField.java +++ b/compiler/src/main/java/dagger/internal/codegen/FrameworkField.java @@ -19,20 +19,20 @@ import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; import com.google.common.base.CaseFormat; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import dagger.MembersInjector; -import dagger.internal.codegen.ContributionBinding.BindingType; import dagger.internal.codegen.writer.ClassName; import dagger.internal.codegen.writer.ParameterizedTypeName; import dagger.internal.codegen.writer.TypeNames; -import dagger.producers.Producer; -import javax.inject.Provider; import javax.lang.model.element.ElementVisitor; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementKindVisitor6; +import static com.google.common.collect.Iterables.any; +import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.internal.codegen.ContributionBinding.contributionTypeFor; + /** * A value object that represents a field used by Dagger-generated code. * @@ -64,26 +64,24 @@ abstract class FrameworkField { } static FrameworkField createForSyntheticContributionBinding( - BindingKey bindingKey, int contributionNumber, ContributionBinding contributionBinding) { - switch (contributionBinding.bindingType()) { + int contributionNumber, ContributionBinding contributionBinding) { + switch (contributionBinding.contributionType()) { case MAP: return createForMapBindingContribution( contributionBinding.frameworkClass(), - BindingKey.create(bindingKey.kind(), contributionBinding.key()), - KeyVariableNamer.INSTANCE.apply(bindingKey.key()) - + "Contribution" + contributionNumber); + contributionBinding.bindingKey(), + KeyVariableNamer.INSTANCE.apply(contributionBinding.key()) + + "Contribution" + + contributionNumber); + case SET: - return createWithTypeFromKey( - contributionBinding.frameworkClass(), - bindingKey, - KeyVariableNamer.INSTANCE.apply(bindingKey.key()) - + "Contribution" + contributionNumber); case UNIQUE: return createWithTypeFromKey( contributionBinding.frameworkClass(), - bindingKey, - KeyVariableNamer.INSTANCE.apply(bindingKey.key()) - + "Contribution" + contributionNumber); + contributionBinding.bindingKey(), + KeyVariableNamer.INSTANCE.apply(contributionBinding.key()) + + "Contribution" + + contributionNumber); default: throw new AssertionError(); } @@ -93,10 +91,9 @@ abstract class FrameworkField { BindingKey bindingKey = resolvedBindings.bindingKey(); switch (bindingKey.kind()) { case CONTRIBUTION: - ImmutableSet<? extends ContributionBinding> contributionBindings = + ImmutableSet<ContributionBinding> contributionBindings = resolvedBindings.contributionBindings(); - BindingType bindingsType = ProvisionBinding.bindingTypeFor(contributionBindings); - switch (bindingsType) { + switch (contributionTypeFor(contributionBindings)) { case SET: case MAP: return createWithTypeFromKey( @@ -104,7 +101,7 @@ abstract class FrameworkField { bindingKey, KeyVariableNamer.INSTANCE.apply(bindingKey.key())); case UNIQUE: - ContributionBinding binding = Iterables.getOnlyElement(contributionBindings); + ContributionBinding binding = getOnlyElement(contributionBindings); return createWithTypeFromKey( FrameworkField.frameworkClassForResolvedBindings(resolvedBindings), bindingKey, @@ -118,7 +115,9 @@ abstract class FrameworkField { bindingKey, CaseFormat.UPPER_CAMEL.to( CaseFormat.LOWER_CAMEL, - Iterables.getOnlyElement(resolvedBindings.bindings()) + resolvedBindings + .membersInjectionBinding() + .get() .bindingElement() .getSimpleName() .toString())); @@ -149,12 +148,9 @@ abstract class FrameworkField { static Class<?> frameworkClassForResolvedBindings(ResolvedBindings resolvedBindings) { switch (resolvedBindings.bindingKey().kind()) { case CONTRIBUTION: - for (ContributionBinding binding : resolvedBindings.contributionBindings()) { - if (binding instanceof ProductionBinding) { - return Producer.class; - } - } - return Provider.class; + return any(resolvedBindings.contributionBindings(), Binding.Type.PRODUCTION) + ? Binding.Type.PRODUCTION.frameworkClass() + : Binding.Type.PROVISION.frameworkClass(); case MEMBERS_INJECTION: return MembersInjector.class; default: diff --git a/compiler/src/main/java/dagger/internal/codegen/InjectBindingRegistry.java b/compiler/src/main/java/dagger/internal/codegen/InjectBindingRegistry.java index b8e302e9a..f7ca429f3 100644 --- a/compiler/src/main/java/dagger/internal/codegen/InjectBindingRegistry.java +++ b/compiler/src/main/java/dagger/internal/codegen/InjectBindingRegistry.java @@ -45,7 +45,7 @@ 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.SourceFiles.membersInjectorNameForType; +import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding; /** * Maintains the collection of provision bindings from {@link Inject} constructors and members @@ -85,15 +85,6 @@ final class InjectBindingRegistry { return bindingsByKey.get(key); } - /** Caches the binding and pretends a binding is generated without actually generating it. */ - B pretendBindingGenerated(B binding, ClassName factoryName) { - tryToCacheBinding(binding); - if (shouldGenerateBinding(binding, factoryName)) { - materializedBindingKeys.add(binding.key()); - } - return binding; - } - /** Caches the binding and generates it if it needs generation. */ void tryRegisterBinding(B binding, ClassName factoryName, boolean explicit) { tryToCacheBinding(binding); @@ -180,7 +171,7 @@ final class InjectBindingRegistry { * attempt to register an unresolved version of it. */ private ProvisionBinding registerBinding(ProvisionBinding binding, boolean explicit) { - ClassName factoryName = SourceFiles.factoryNameForProvisionBinding(binding); + ClassName factoryName = generatedClassNameForBinding(binding); provisionBindings.tryRegisterBinding(binding, factoryName, explicit); if (binding.hasNonDefaultTypeParameters()) { provisionBindings.tryToGenerateBinding(provisionBindingFactory.unresolve(binding), @@ -195,21 +186,11 @@ final class InjectBindingRegistry { */ private MembersInjectionBinding registerBinding( MembersInjectionBinding binding, boolean explicit) { - ClassName membersInjectorName = membersInjectorNameForType(binding.bindingTypeElement()); - if (binding.injectionSites().isEmpty()) { - // empty members injection bindings are special and don't need source files. - // so, we just pretend - membersInjectionBindings.pretendBindingGenerated(binding, membersInjectorName); - if (binding.hasNonDefaultTypeParameters()) { - membersInjectionBindings.pretendBindingGenerated( - membersInjectionBindingFactory.unresolve(binding), membersInjectorName); - } - } else { - membersInjectionBindings.tryRegisterBinding(binding, membersInjectorName, explicit); - if (binding.hasNonDefaultTypeParameters()) { - membersInjectionBindings.tryToGenerateBinding( - membersInjectionBindingFactory.unresolve(binding), membersInjectorName, explicit); - } + ClassName membersInjectorName = generatedClassNameForBinding(binding); + membersInjectionBindings.tryRegisterBinding(binding, membersInjectorName, explicit); + if (binding.hasNonDefaultTypeParameters()) { + membersInjectionBindings.tryToGenerateBinding( + membersInjectionBindingFactory.unresolve(binding), membersInjectorName, explicit); } return binding; } diff --git a/compiler/src/main/java/dagger/internal/codegen/InjectFieldValidator.java b/compiler/src/main/java/dagger/internal/codegen/InjectFieldValidator.java index 91143de6e..e30678af7 100644 --- a/compiler/src/main/java/dagger/internal/codegen/InjectFieldValidator.java +++ b/compiler/src/main/java/dagger/internal/codegen/InjectFieldValidator.java @@ -16,7 +16,6 @@ package dagger.internal.codegen; import com.google.common.collect.ImmutableSet; - import java.util.Set; import javax.inject.Inject; import javax.lang.model.element.AnnotationMirror; diff --git a/compiler/src/main/java/dagger/internal/codegen/InjectProcessingStep.java b/compiler/src/main/java/dagger/internal/codegen/InjectProcessingStep.java index 61b19c96c..dac904fdc 100644 --- a/compiler/src/main/java/dagger/internal/codegen/InjectProcessingStep.java +++ b/compiler/src/main/java/dagger/internal/codegen/InjectProcessingStep.java @@ -20,8 +20,10 @@ import com.google.auto.common.MoreTypes; import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; import com.google.common.collect.SetMultimap; + import java.lang.annotation.Annotation; import java.util.Set; + import javax.annotation.processing.Messager; import javax.inject.Inject; import javax.lang.model.element.Element; @@ -73,64 +75,71 @@ final class InjectProcessingStep implements BasicAnnotationProcessor.ProcessingS @Override public Set<Element> process( SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + ImmutableSet.Builder<Element> rejectedElements = ImmutableSet.builder(); // TODO(gak): add some error handling for bad source files final ImmutableSet.Builder<ProvisionBinding> provisions = ImmutableSet.builder(); // TODO(gak): instead, we should collect reports by type and check later final ImmutableSet.Builder<DeclaredType> membersInjectedTypes = ImmutableSet.builder(); for (Element injectElement : elementsByAnnotation.get(Inject.class)) { - injectElement.accept( - new ElementKindVisitor6<Void, Void>() { - @Override - public Void visitExecutableAsConstructor(ExecutableElement constructorElement, Void v) { - ValidationReport<TypeElement> report = - constructorValidator.validate(constructorElement); - - report.printMessagesTo(messager); - - if (report.isClean()) { - provisions.add( - provisionBindingFactory.forInjectConstructor( - constructorElement, Optional.<TypeMirror>absent())); - DeclaredType type = - MoreTypes.asDeclared(constructorElement.getEnclosingElement().asType()); - if (membersInjectionBindingFactory.hasInjectedMembers(type)) { - membersInjectedTypes.add(type); + try { + injectElement.accept( + new ElementKindVisitor6<Void, Void>() { + @Override + public Void visitExecutableAsConstructor( + ExecutableElement constructorElement, Void v) { + ValidationReport<TypeElement> report = + constructorValidator.validate(constructorElement); + + report.printMessagesTo(messager); + + if (report.isClean()) { + provisions.add( + provisionBindingFactory.forInjectConstructor( + constructorElement, Optional.<TypeMirror>absent())); + DeclaredType type = + MoreTypes.asDeclared(constructorElement.getEnclosingElement().asType()); + if (membersInjectionBindingFactory.hasInjectedMembers(type)) { + membersInjectedTypes.add(type); + } } + + return null; } - return null; - } + @Override + public Void visitVariableAsField(VariableElement fieldElement, Void p) { + ValidationReport<VariableElement> report = fieldValidator.validate(fieldElement); - @Override - public Void visitVariableAsField(VariableElement fieldElement, Void p) { - ValidationReport<VariableElement> report = fieldValidator.validate(fieldElement); + report.printMessagesTo(messager); - report.printMessagesTo(messager); + if (report.isClean()) { + membersInjectedTypes.add( + MoreTypes.asDeclared(fieldElement.getEnclosingElement().asType())); + } - if (report.isClean()) { - membersInjectedTypes.add( - MoreTypes.asDeclared(fieldElement.getEnclosingElement().asType())); + return null; } - return null; - } + @Override + public Void visitExecutableAsMethod(ExecutableElement methodElement, Void p) { + ValidationReport<ExecutableElement> report = + methodValidator.validate(methodElement); - @Override - public Void visitExecutableAsMethod(ExecutableElement methodElement, Void p) { - ValidationReport<ExecutableElement> report = methodValidator.validate(methodElement); + report.printMessagesTo(messager); - report.printMessagesTo(messager); + if (report.isClean()) { + membersInjectedTypes.add( + MoreTypes.asDeclared(methodElement.getEnclosingElement().asType())); + } - if (report.isClean()) { - membersInjectedTypes.add( - MoreTypes.asDeclared(methodElement.getEnclosingElement().asType())); + return null; } - - return null; - } - }, - null); + }, + null); + } catch (TypeNotPresentException e) { + rejectedElements.add(injectElement); + } } for (DeclaredType injectedType : membersInjectedTypes.build()) { @@ -141,6 +150,6 @@ final class InjectProcessingStep implements BasicAnnotationProcessor.ProcessingS for (ProvisionBinding binding : provisions.build()) { injectBindingRegistry.registerBinding(binding); } - return ImmutableSet.of(); + return rejectedElements.build(); } } diff --git a/compiler/src/main/java/dagger/internal/codegen/Key.java b/compiler/src/main/java/dagger/internal/codegen/Key.java index c14cc22c4..f0bd3a04f 100644 --- a/compiler/src/main/java/dagger/internal/codegen/Key.java +++ b/compiler/src/main/java/dagger/internal/codegen/Key.java @@ -25,6 +25,7 @@ import com.google.common.base.Optional; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.ListenableFuture; import dagger.Provides; +import dagger.producers.Produced; import dagger.producers.Producer; import dagger.producers.Produces; import java.util.Map; @@ -45,6 +46,7 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleTypeVisitor6; import javax.lang.model.util.Types; +import static com.google.auto.common.MoreTypes.asExecutable; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.InjectionAnnotations.getQualifier; @@ -200,6 +202,16 @@ abstract class Key { return forMethod(componentMethod, keyType); } + Key forSubcomponentBuilderMethod( + ExecutableElement subcomponentBuilderMethod, DeclaredType declaredContainer) { + checkNotNull(subcomponentBuilderMethod); + checkArgument(subcomponentBuilderMethod.getKind().equals(METHOD)); + ExecutableType resolvedMethod = + asExecutable(types.asMemberOf(declaredContainer, subcomponentBuilderMethod)); + TypeMirror returnType = normalize(types, resolvedMethod.getReturnType()); + return forMethod(subcomponentBuilderMethod, returnType); + } + Key forProvidesMethod(ExecutableType executableType, ExecutableElement method) { checkNotNull(method); checkArgument(method.getKind().equals(METHOD)); @@ -334,7 +346,7 @@ abstract class Key { DeclaredType declaredMapType = MoreTypes.asDeclared(possibleMapKey.type()); TypeMirror mapValueType = Util.getValueTypeOfMap(declaredMapType); if (!MoreTypes.isTypeOf(wrappingClass, mapValueType)) { - DeclaredType keyType = Util.getKeyTypeOfMap(declaredMapType); + TypeMirror keyType = Util.getKeyTypeOfMap(declaredMapType); TypeElement wrappingElement = getClassElement(wrappingClass); if (wrappingElement == null) { // This target might not be compiled with Producers, so wrappingClass might not have an @@ -350,5 +362,22 @@ abstract class Key { } return Optional.absent(); } + + /** + * Optionally extract a {@link Key} for a {@code Set<T>} if the given key is for + * {@code Set<Produced<T>>}. + */ + Optional<Key> implicitSetKeyFromProduced(Key possibleSetOfProducedKey) { + if (MoreTypes.isTypeOf(Set.class, possibleSetOfProducedKey.type())) { + TypeMirror argType = + MoreTypes.asDeclared(possibleSetOfProducedKey.type()).getTypeArguments().get(0); + if (MoreTypes.isTypeOf(Produced.class, argType)) { + TypeMirror producedArgType = MoreTypes.asDeclared(argType).getTypeArguments().get(0); + TypeMirror setType = types.getDeclaredType(getSetElement(), producedArgType); + return Optional.of(possibleSetOfProducedKey.withType(types, setType)); + } + } + return Optional.absent(); + } } } diff --git a/compiler/src/main/java/dagger/internal/codegen/KeyFormatter.java b/compiler/src/main/java/dagger/internal/codegen/KeyFormatter.java index d2e62fcab..6e695f33a 100644 --- a/compiler/src/main/java/dagger/internal/codegen/KeyFormatter.java +++ b/compiler/src/main/java/dagger/internal/codegen/KeyFormatter.java @@ -26,7 +26,7 @@ final class KeyFormatter extends Formatter<Key> { @Override public String format(Key request) { StringBuilder builder = new StringBuilder(); if (request.qualifier().isPresent()) { - builder.append(request.qualifier()); // TODO(cgruber): Use AnnotationMirrorFormatter. + builder.append(request.qualifier().get()); builder.append(' '); } builder.append(request.type()); // TODO(cgruber): Use TypeMirrorFormatter. diff --git a/compiler/src/main/java/dagger/internal/codegen/MapKeyValidator.java b/compiler/src/main/java/dagger/internal/codegen/MapKeyValidator.java index d6aa2f21c..586a1e935 100644 --- a/compiler/src/main/java/dagger/internal/codegen/MapKeyValidator.java +++ b/compiler/src/main/java/dagger/internal/codegen/MapKeyValidator.java @@ -28,7 +28,7 @@ import static dagger.internal.codegen.ErrorMessages.UNWRAPPED_MAP_KEY_WITH_TOO_M import static javax.lang.model.util.ElementFilter.methodsIn; /** - * A {@link Validator} for {@link MapKey} annotations. + * A validator for {@link MapKey} annotations. * * @author Chenying Hou * @since 2.0 diff --git a/compiler/src/main/java/dagger/internal/codegen/MapKeys.java b/compiler/src/main/java/dagger/internal/codegen/MapKeys.java index 4d79a28c8..fbbd8cf82 100644 --- a/compiler/src/main/java/dagger/internal/codegen/MapKeys.java +++ b/compiler/src/main/java/dagger/internal/codegen/MapKeys.java @@ -20,7 +20,6 @@ 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.google.common.collect.Iterables; import dagger.MapKey; import dagger.internal.codegen.writer.ClassName; import dagger.internal.codegen.writer.Snippet; diff --git a/compiler/src/main/java/dagger/internal/codegen/MembersInjectionBinding.java b/compiler/src/main/java/dagger/internal/codegen/MembersInjectionBinding.java index 87b2ab187..7fbcf115d 100644 --- a/compiler/src/main/java/dagger/internal/codegen/MembersInjectionBinding.java +++ b/compiler/src/main/java/dagger/internal/codegen/MembersInjectionBinding.java @@ -89,6 +89,11 @@ abstract class MembersInjectionBinding extends Binding { Optional.<DependencyRequest>absent()); } + @Override + protected Binding.Type bindingType() { + return Binding.Type.MEMBERS_INJECTION; + } + @AutoValue abstract static class InjectionSite { enum Kind { diff --git a/compiler/src/main/java/dagger/internal/codegen/MembersInjectorGenerator.java b/compiler/src/main/java/dagger/internal/codegen/MembersInjectorGenerator.java index f7fcdee9d..3694d2e81 100644 --- a/compiler/src/main/java/dagger/internal/codegen/MembersInjectorGenerator.java +++ b/compiler/src/main/java/dagger/internal/codegen/MembersInjectorGenerator.java @@ -62,7 +62,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.SourceFiles.frameworkTypeUsageStatement; import static dagger.internal.codegen.SourceFiles.membersInjectorNameForType; -import static dagger.internal.codegen.SourceFiles.parameterizedMembersInjectorNameForMembersInjectionBinding; +import static dagger.internal.codegen.SourceFiles.parameterizedGeneratedTypeNameForBinding; import static dagger.internal.codegen.writer.Snippet.makeParametersSnippet; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; @@ -109,6 +109,10 @@ final class MembersInjectorGenerator extends SourceFileGenerator<MembersInjectio @Override ImmutableSet<JavaWriter> write(ClassName generatedTypeName, MembersInjectionBinding binding) { + // Empty members injection bindings are special and don't need source files. + if (binding.injectionSites().isEmpty()) { + return ImmutableSet.of(); + } Set<String> delegateMethods = new HashSet<>(); // We don't want to write out resolved bindings -- we want to write out the generic version. @@ -197,7 +201,7 @@ final class MembersInjectorGenerator extends SourceFileGenerator<MembersInjectio .body() .addSnippet( " return new %s(%s);", - parameterizedMembersInjectorNameForMembersInjectionBinding(binding), + parameterizedGeneratedTypeNameForBinding(binding), Joiner.on(", ").join(constructorWriter.parameters().keySet())); ImmutableMap<BindingKey, FieldWriter> dependencyFields = dependencyFieldsBuilder.build(); diff --git a/compiler/src/main/java/dagger/internal/codegen/MissingBindingSuggestions.java b/compiler/src/main/java/dagger/internal/codegen/MissingBindingSuggestions.java index dcabab52d..4b6f0c33c 100644 --- a/compiler/src/main/java/dagger/internal/codegen/MissingBindingSuggestions.java +++ b/compiler/src/main/java/dagger/internal/codegen/MissingBindingSuggestions.java @@ -15,9 +15,7 @@ */ package dagger.internal.codegen; -import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; - import java.util.ArrayDeque; import java.util.Deque; diff --git a/compiler/src/main/java/dagger/internal/codegen/ModuleDescriptor.java b/compiler/src/main/java/dagger/internal/codegen/ModuleDescriptor.java index f5e33b59d..c938af2c3 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ModuleDescriptor.java +++ b/compiler/src/main/java/dagger/internal/codegen/ModuleDescriptor.java @@ -42,7 +42,7 @@ abstract class ModuleDescriptor { abstract ImmutableSet<ModuleDescriptor> includedModules(); - abstract ImmutableSet<? extends ContributionBinding> bindings(); + abstract ImmutableSet<ContributionBinding> bindings(); enum DefaultCreationStrategy { PASSED, diff --git a/compiler/src/main/java/dagger/internal/codegen/MonitoringModuleGenerator.java b/compiler/src/main/java/dagger/internal/codegen/MonitoringModuleGenerator.java new file mode 100644 index 000000000..a4e020b32 --- /dev/null +++ b/compiler/src/main/java/dagger/internal/codegen/MonitoringModuleGenerator.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dagger.internal.codegen; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableSet; +import dagger.Module; +import dagger.Provides; +import dagger.internal.codegen.writer.ClassName; +import dagger.internal.codegen.writer.ClassWriter; +import dagger.internal.codegen.writer.FieldWriter; +import dagger.internal.codegen.writer.JavaWriter; +import dagger.internal.codegen.writer.MethodWriter; +import dagger.internal.codegen.writer.ParameterizedTypeName; +import dagger.internal.codegen.writer.TypeName; +import dagger.producers.monitoring.ProductionComponentMonitor; +import dagger.producers.monitoring.internal.MonitorCache; + +import java.util.Set; +import javax.annotation.Generated; +import javax.annotation.processing.Filer; +import javax.inject.Provider; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + +import static javax.lang.model.element.Modifier.PRIVATE; +import static javax.lang.model.element.Modifier.STATIC; +import static javax.lang.model.element.Modifier.FINAL; + +/** Generates a monitoring module for use with production components. */ +final class MonitoringModuleGenerator extends SourceFileGenerator<TypeElement> { + private static final TypeName SET_OF_FACTORIES = + ParameterizedTypeName.create( + Set.class, ClassName.fromClass(ProductionComponentMonitor.Factory.class)); + + MonitoringModuleGenerator(Filer filer) { + super(filer); + } + + @Override + ClassName nameGeneratedType(TypeElement componentElement) { + return SourceFiles.generatedMonitoringModuleName(componentElement); + } + + @Override + Iterable<? extends Element> getOriginatingElements(TypeElement componentElement) { + return ImmutableSet.of(componentElement); + } + + @Override + Optional<? extends Element> getElementForErrorReporting(TypeElement componentElement) { + return Optional.of(componentElement); + } + + @Override + ImmutableSet<JavaWriter> write(ClassName generatedTypeName, TypeElement componentElement) { + JavaWriter writer = JavaWriter.inPackage(generatedTypeName.packageName()); + ClassWriter classWriter = writer.addClass(generatedTypeName.simpleName()); + classWriter.annotate(Generated.class).setValue(ComponentProcessor.class.getName()); + classWriter.annotate(Module.class); + classWriter.addModifiers(FINAL); + + // TODO(beder): Replace this default set binding with EmptyCollections when it exists. + MethodWriter emptySetBindingMethod = + classWriter.addMethod(SET_OF_FACTORIES, "defaultSetOfFactories"); + emptySetBindingMethod.addModifiers(STATIC); + emptySetBindingMethod.annotate(Provides.class).setMember("type", Provides.Type.SET_VALUES); + emptySetBindingMethod + .body() + .addSnippet("return %s.of();", ClassName.fromClass(ImmutableSet.class)); + + FieldWriter providerField = classWriter.addField(MonitorCache.class, "monitorCache"); + providerField.addModifiers(PRIVATE, FINAL); + providerField.setInitializer("new %s()", ClassName.fromClass(MonitorCache.class)); + MethodWriter monitorMethod = classWriter.addMethod(ProductionComponentMonitor.class, "monitor"); + monitorMethod.annotate(Provides.class); + monitorMethod.addParameter( + ParameterizedTypeName.create(Provider.class, ClassName.fromTypeElement(componentElement)), + "component"); + monitorMethod.addParameter( + ParameterizedTypeName.create(Provider.class, SET_OF_FACTORIES), "factories"); + monitorMethod.body().addSnippet("return monitorCache.monitor(component, factories);"); + + return ImmutableSet.of(writer); + } +} diff --git a/compiler/src/main/java/dagger/internal/codegen/MonitoringModuleProcessingStep.java b/compiler/src/main/java/dagger/internal/codegen/MonitoringModuleProcessingStep.java new file mode 100644 index 000000000..91b10e7ef --- /dev/null +++ b/compiler/src/main/java/dagger/internal/codegen/MonitoringModuleProcessingStep.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dagger.internal.codegen; + +import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.SetMultimap; +import dagger.producers.ProductionComponent; +import java.lang.annotation.Annotation; +import java.util.Set; +import javax.annotation.processing.Messager; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + +import static javax.lang.model.util.ElementFilter.typesIn; + +/** + * A processing step that is responsible for generating a special module for a + * {@link ProductionComponent}. + */ +final class MonitoringModuleProcessingStep implements ProcessingStep { + private final Messager messager; + private final MonitoringModuleGenerator monitoringModuleGenerator; + + MonitoringModuleProcessingStep( + Messager messager, MonitoringModuleGenerator monitoringModuleGenerator) { + this.messager = messager; + this.monitoringModuleGenerator = monitoringModuleGenerator; + } + + @Override + public Set<? extends Class<? extends Annotation>> annotations() { + return ImmutableSet.of(ProductionComponent.class); + } + + @Override + public Set<Element> process( + SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + for (TypeElement element : typesIn(elementsByAnnotation.get(ProductionComponent.class))) { + try { + monitoringModuleGenerator.generate(element); + } catch (SourceFileGenerationException e) { + e.printMessageTo(messager); + } + } + return ImmutableSet.of(); + } +} diff --git a/compiler/src/main/java/dagger/internal/codegen/ProducerFactoryGenerator.java b/compiler/src/main/java/dagger/internal/codegen/ProducerFactoryGenerator.java index 9ae154886..90a0337bd 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ProducerFactoryGenerator.java +++ b/compiler/src/main/java/dagger/internal/codegen/ProducerFactoryGenerator.java @@ -44,19 +44,17 @@ import dagger.producers.Producer; import dagger.producers.Produces; import dagger.producers.internal.AbstractProducer; import dagger.producers.internal.Producers; +import dagger.producers.monitoring.ProducerMonitor; import dagger.producers.monitoring.ProducerToken; -import dagger.producers.monitoring.ProductionComponentMonitor; import java.util.List; -import java.util.concurrent.Callable; import java.util.concurrent.Executor; import javax.annotation.Generated; -import javax.annotation.Nullable; import javax.annotation.processing.Filer; import javax.lang.model.element.Element; import javax.lang.model.type.TypeMirror; -import static dagger.internal.codegen.SourceFiles.factoryNameForProductionBinding; import static dagger.internal.codegen.SourceFiles.frameworkTypeUsageStatement; +import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding; import static dagger.internal.codegen.writer.Snippet.makeParametersSnippet; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; @@ -80,7 +78,7 @@ final class ProducerFactoryGenerator extends SourceFileGenerator<ProductionBindi @Override ClassName nameGeneratedType(ProductionBinding binding) { - return factoryNameForProductionBinding(binding); + return generatedClassNameForBinding(binding); } @Override @@ -107,14 +105,15 @@ final class ProducerFactoryGenerator extends SourceFileGenerator<ProductionBindi ConstructorWriter constructorWriter = factoryWriter.addConstructor(); constructorWriter.addModifiers(PUBLIC); - constructorWriter - .addParameter(ProductionComponentMonitor.class, "componentMonitor") - .annotate(Nullable.class); + ImmutableMap<BindingKey, FrameworkField> fields = + SourceFiles.generateBindingFieldsForDependencies( + dependencyRequestMapper, binding.implicitDependencies()); + constructorWriter .body() .addSnippet( - "super(%s.producerMonitorFor(componentMonitor, %s.create(%s.class)));", - ClassName.fromClass(Producers.class), + "super(%s, %s.create(%s.class));", + fields.get(binding.monitorRequest().get().bindingKey()).name(), ClassName.fromClass(ProducerToken.class), factoryWriter.name()); @@ -140,13 +139,10 @@ final class ProducerFactoryGenerator extends SourceFileGenerator<ProductionBindi factoryWriter.setSuperclass( ParameterizedTypeName.create(AbstractProducer.class, providedTypeName)); - MethodWriter getMethodWriter = factoryWriter.addMethod(futureTypeName, "compute"); - getMethodWriter.annotate(Override.class); - getMethodWriter.addModifiers(PROTECTED); - - final ImmutableMap<BindingKey, FrameworkField> fields = - SourceFiles.generateBindingFieldsForDependencies( - dependencyRequestMapper, binding.dependencies()); + MethodWriter computeMethodWriter = factoryWriter.addMethod(futureTypeName, "compute"); + computeMethodWriter.annotate(Override.class); + computeMethodWriter.addModifiers(PROTECTED); + computeMethodWriter.addParameter(ProducerMonitor.class, "monitor").addModifiers(FINAL); for (FrameworkField bindingField : fields.values()) { TypeName fieldType = bindingField.frameworkType(); @@ -158,15 +154,18 @@ final class ProducerFactoryGenerator extends SourceFileGenerator<ProductionBindi .addSnippet("this.%1$s = %1$s;", field.name()); } - boolean returnsFuture = binding.bindingKind().equals(ProductionBinding.Kind.FUTURE_PRODUCTION); - ImmutableList<DependencyRequest> asyncDependencies = FluentIterable - .from(binding.dependencies()) - .filter(new Predicate<DependencyRequest>() { - @Override public boolean apply(DependencyRequest dependency) { - return isAsyncDependency(dependency); - } - }) - .toList(); + boolean returnsFuture = + binding.bindingKind().equals(ContributionBinding.Kind.FUTURE_PRODUCTION); + ImmutableList<DependencyRequest> asyncDependencies = + FluentIterable.from(binding.implicitDependencies()) + .filter( + new Predicate<DependencyRequest>() { + @Override + public boolean apply(DependencyRequest dependency) { + return isAsyncDependency(dependency); + } + }) + .toList(); for (DependencyRequest dependency : asyncDependencies) { ParameterizedTypeName futureType = ParameterizedTypeName.create( @@ -174,143 +173,229 @@ final class ProducerFactoryGenerator extends SourceFileGenerator<ProductionBindi asyncDependencyType(dependency)); String name = fields.get(dependency.bindingKey()).name(); Snippet futureAccess = Snippet.format("%s.get()", name); - getMethodWriter.body().addSnippet("%s %sFuture = %s;", - futureType, - name, - dependency.kind().equals(DependencyRequest.Kind.PRODUCED) - ? Snippet.format("%s.createFutureProduced(%s)", - ClassName.fromClass(Producers.class), futureAccess) - : futureAccess); + computeMethodWriter + .body() + .addSnippet( + "%s %sFuture = %s;", + futureType, + name, + dependency.kind().equals(DependencyRequest.Kind.PRODUCED) + ? Snippet.format( + "%s.createFutureProduced(%s)", + ClassName.fromClass(Producers.class), + futureAccess) + : futureAccess); + } + + FutureTransform futureTransform = FutureTransform.create(fields, binding, asyncDependencies); + Snippet transformSnippet = + Snippet.format( + Joiner.on('\n') + .join( + "new %1$s<%2$s, %3$s>() {", + " %4$s", + " @Override public %5$s apply(%2$s %6$s) %7$s {", + " %8$s", + " }", + "}"), + ClassName.fromClass(AsyncFunction.class), + futureTransform.applyArgType(), + providedTypeName, + futureTransform.hasUncheckedCast() + ? "@SuppressWarnings(\"unchecked\") // safe by specification" + : "", + futureTypeName, + futureTransform.applyArgName(), + getThrowsClause(binding.thrownTypes()), + getInvocationSnippet(!returnsFuture, binding, futureTransform.parameterSnippets())); + computeMethodWriter + .body() + .addSnippet( + "return %s.transform(%s, %s, executor);", + ClassName.fromClass(Futures.class), + futureTransform.futureSnippet(), + transformSnippet); + + // TODO(gak): write a sensible toString + return ImmutableSet.of(writer); + } + + /** Represents the transformation of an input future by a producer method. */ + abstract static class FutureTransform { + protected final ImmutableMap<BindingKey, FrameworkField> fields; + protected final ProductionBinding binding; + + FutureTransform(ImmutableMap<BindingKey, FrameworkField> fields, ProductionBinding binding) { + this.fields = fields; + this.binding = binding; + } + + /** The snippet representing the future that should be transformed. */ + abstract Snippet futureSnippet(); + + /** The type of the argument to the apply method. */ + abstract TypeName applyArgType(); + + /** The name of the argument to the apply method */ + abstract String applyArgName(); + + /** The snippets to be passed to the produces method itself. */ + abstract ImmutableList<Snippet> parameterSnippets(); + + /** Whether the transform method has an unchecked cast. */ + boolean hasUncheckedCast() { + return false; + } + + static FutureTransform create( + ImmutableMap<BindingKey, FrameworkField> fields, + ProductionBinding binding, + ImmutableList<DependencyRequest> asyncDependencies) { + if (asyncDependencies.isEmpty()) { + return new NoArgFutureTransform(fields, binding); + } else if (asyncDependencies.size() == 1) { + return new SingleArgFutureTransform( + fields, binding, Iterables.getOnlyElement(asyncDependencies)); + } else { + return new MultiArgFutureTransform(fields, binding, asyncDependencies); + } + } + } + + static final class NoArgFutureTransform extends FutureTransform { + NoArgFutureTransform( + ImmutableMap<BindingKey, FrameworkField> fields, ProductionBinding binding) { + super(fields, binding); + } + + @Override + Snippet futureSnippet() { + return Snippet.format( + "%s.<%s>immediateFuture(null)", + ClassName.fromClass(Futures.class), + ClassName.fromClass(Void.class)); + } + + @Override + TypeName applyArgType() { + return ClassName.fromClass(Void.class); + } + + @Override + String applyArgName() { + return "ignoredVoidArg"; } - if (asyncDependencies.isEmpty()) { + @Override + ImmutableList<Snippet> parameterSnippets() { ImmutableList.Builder<Snippet> parameterSnippets = ImmutableList.builder(); for (DependencyRequest dependency : binding.dependencies()) { - parameterSnippets.add(frameworkTypeUsageStatement( - Snippet.format(fields.get(dependency.bindingKey()).name()), dependency.kind())); + parameterSnippets.add( + frameworkTypeUsageStatement( + Snippet.format( + "%s", fields.get(dependency.bindingKey()).name()), dependency.kind())); } - final boolean wrapWithFuture = false; // since submitToExecutor will create the future - Snippet invocationSnippet = - getInvocationSnippet(wrapWithFuture, binding, parameterSnippets.build()); - TypeName callableReturnType = returnsFuture ? futureTypeName : providedTypeName; - Snippet throwsClause = getThrowsClause(binding.thrownTypes()); - Snippet callableSnippet = - Snippet.format( - Joiner.on('\n') - .join( - "new %1$s<%2$s>() {", - " @Override public %2$s call() %3$s{", - " %4$s", - " }", - "}"), - ClassName.fromClass(Callable.class), - callableReturnType, - throwsClause, - invocationSnippet); - getMethodWriter - .body() - .addSnippet( - "%s future = %s.submitToExecutor(%s, executor);", - ParameterizedTypeName.create( - ClassName.fromClass(ListenableFuture.class), callableReturnType), - ClassName.fromClass(Producers.class), - callableSnippet); - getMethodWriter - .body() - .addSnippet( - "return %s;", - returnsFuture - ? Snippet.format("%s.dereference(future)", ClassName.fromClass(Futures.class)) - : "future"); - } else { - final Snippet futureSnippet; - final Snippet transformSnippet; - if (asyncDependencies.size() == 1) { - DependencyRequest asyncDependency = Iterables.getOnlyElement(asyncDependencies); - futureSnippet = Snippet.format("%s", - fields.get(asyncDependency.bindingKey()).name() + "Future"); - String argName = asyncDependency.requestElement().getSimpleName().toString(); - ImmutableList.Builder<Snippet> parameterSnippets = ImmutableList.builder(); - for (DependencyRequest dependency : binding.dependencies()) { - // We really want to compare instances here, because asyncDependency is an element in the - // set binding.dependencies(). - if (dependency == asyncDependency) { - parameterSnippets.add(Snippet.format("%s", argName)); - } else { - parameterSnippets.add(frameworkTypeUsageStatement( - Snippet.format(fields.get(dependency.bindingKey()).name()), - dependency.kind())); - } + return parameterSnippets.build(); + } + } + + static final class SingleArgFutureTransform extends FutureTransform { + private final DependencyRequest asyncDependency; + + SingleArgFutureTransform( + ImmutableMap<BindingKey, FrameworkField> fields, + ProductionBinding binding, + DependencyRequest asyncDependency) { + super(fields, binding); + this.asyncDependency = asyncDependency; + } + + @Override + Snippet futureSnippet() { + return Snippet.format("%s", fields.get(asyncDependency.bindingKey()).name() + "Future"); + } + + @Override + TypeName applyArgType() { + return asyncDependencyType(asyncDependency); + } + + @Override + String applyArgName() { + return asyncDependency.requestElement().getSimpleName().toString(); + } + + @Override + ImmutableList<Snippet> parameterSnippets() { + ImmutableList.Builder<Snippet> parameterSnippets = ImmutableList.builder(); + for (DependencyRequest dependency : binding.dependencies()) { + // We really want to compare instances here, because asyncDependency is an element in the + // set binding.dependencies(). + if (dependency == asyncDependency) { + parameterSnippets.add(Snippet.format("%s", applyArgName())); + } else { + parameterSnippets.add( + frameworkTypeUsageStatement( + Snippet.format( + "%s", fields.get(dependency.bindingKey()).name()), dependency.kind())); } - boolean wrapWithFuture = !returnsFuture; // only wrap if we don't already have a future - Snippet invocationSnippet = - getInvocationSnippet(wrapWithFuture, binding, parameterSnippets.build()); - Snippet throwsClause = getThrowsClause(binding.thrownTypes()); - transformSnippet = - Snippet.format( - Joiner.on('\n') - .join( - "new %1$s<%2$s, %3$s>() {", - " @Override public %4$s apply(%2$s %5$s) %6$s{", - " %7$s", - " }", - "}"), - ClassName.fromClass(AsyncFunction.class), - asyncDependencyType(asyncDependency), - providedTypeName, - futureTypeName, - argName, - throwsClause, - invocationSnippet); - } else { - futureSnippet = Snippet.format("%s.<%s>allAsList(%s)", - ClassName.fromClass(Futures.class), - ClassName.fromClass(Object.class), - Joiner.on(",").join(FluentIterable - .from(asyncDependencies) - .transform(DependencyRequest.BINDING_KEY_FUNCTION) - .transform(new Function<BindingKey, String>() { - @Override public String apply(BindingKey dependencyBindingKey) { - return fields.get(dependencyBindingKey).name() + "Future"; - } - }))); - ImmutableList<Snippet> parameterSnippets = getParameterSnippets(binding, fields, "args"); - boolean wrapWithFuture = !returnsFuture; // only wrap if we don't already have a future - Snippet invocationSnippet = - getInvocationSnippet(wrapWithFuture, binding, parameterSnippets); - ParameterizedTypeName listOfObject = - ParameterizedTypeName.create( - ClassName.fromClass(List.class), ClassName.fromClass(Object.class)); - Snippet throwsClause = getThrowsClause(binding.thrownTypes()); - transformSnippet = - Snippet.format( - Joiner.on('\n') - .join( - "new %1$s<%2$s, %3$s>() {", - " @SuppressWarnings(\"unchecked\") // safe by specification", - " @Override public %4$s apply(%2$s args) %5$s{", - " %6$s", - " }", - "}"), - ClassName.fromClass(AsyncFunction.class), - listOfObject, - providedTypeName, - futureTypeName, - throwsClause, - invocationSnippet); } - getMethodWriter.body().addSnippet("return %s.%s(%s, %s, executor);", + return parameterSnippets.build(); + } + } + + static final class MultiArgFutureTransform extends FutureTransform { + private final ImmutableList<DependencyRequest> asyncDependencies; + + MultiArgFutureTransform( + ImmutableMap<BindingKey, FrameworkField> fields, + ProductionBinding binding, + ImmutableList<DependencyRequest> asyncDependencies) { + super(fields, binding); + this.asyncDependencies = asyncDependencies; + } + + @Override + Snippet futureSnippet() { + return Snippet.format( + "%s.<%s>allAsList(%s)", ClassName.fromClass(Futures.class), - "transform", - futureSnippet, - transformSnippet); + ClassName.fromClass(Object.class), + makeParametersSnippet( + FluentIterable.from(asyncDependencies) + .transform(DependencyRequest.BINDING_KEY_FUNCTION) + .transform( + new Function<BindingKey, Snippet>() { + @Override + public Snippet apply(BindingKey bindingKey) { + return Snippet.format("%s", fields.get(bindingKey).name() + "Future"); + } + }))); } - // TODO(gak): write a sensible toString - return ImmutableSet.of(writer); + @Override + TypeName applyArgType() { + return ParameterizedTypeName.create( + ClassName.fromClass(List.class), ClassName.fromClass(Object.class)); + } + + @Override + String applyArgName() { + return "args"; + } + + @Override + ImmutableList<Snippet> parameterSnippets() { + return getParameterSnippets(binding, fields, applyArgName()); + } + + @Override + boolean hasUncheckedCast() { + return true; + } } - private boolean isAsyncDependency(DependencyRequest dependency) { + private static boolean isAsyncDependency(DependencyRequest dependency) { switch (dependency.kind()) { case INSTANCE: case PRODUCED: @@ -320,7 +405,7 @@ final class ProducerFactoryGenerator extends SourceFileGenerator<ProductionBindi } } - private TypeName asyncDependencyType(DependencyRequest dependency) { + private static TypeName asyncDependencyType(DependencyRequest dependency) { TypeName keyName = TypeNames.forTypeMirror(dependency.key().type()); switch (dependency.kind()) { case INSTANCE: @@ -332,7 +417,8 @@ final class ProducerFactoryGenerator extends SourceFileGenerator<ProductionBindi } } - private ImmutableList<Snippet> getParameterSnippets(ProductionBinding binding, + private static ImmutableList<Snippet> getParameterSnippets( + ProductionBinding binding, ImmutableMap<BindingKey, FrameworkField> fields, String listArgName) { int argIndex = 0; @@ -347,7 +433,7 @@ final class ProducerFactoryGenerator extends SourceFileGenerator<ProductionBindi argIndex++; } else { snippets.add(frameworkTypeUsageStatement( - Snippet.format(fields.get(dependency.bindingKey()).name()), dependency.kind())); + Snippet.format("%s", fields.get(dependency.bindingKey()).name()), dependency.kind())); } } return snippets.build(); @@ -375,11 +461,11 @@ final class ProducerFactoryGenerator extends SourceFileGenerator<ProductionBindi // because we'll wrap all monitoring in non-throwing monitors before we pass them to the // factories. ImmutableList.Builder<Snippet> snippets = ImmutableList.builder(); - snippets.add(Snippet.format("if (monitor != null) { monitor.methodStarting(); }")); + snippets.add(Snippet.format("monitor.methodStarting();")); final Snippet valueSnippet; if (binding.productionType().equals(Produces.Type.SET)) { - if (binding.bindingKind().equals(ProductionBinding.Kind.FUTURE_PRODUCTION)) { + if (binding.bindingKind().equals(ContributionBinding.Kind.FUTURE_PRODUCTION)) { valueSnippet = Snippet.format( "%s.createFutureSingletonSet(%s)", @@ -403,11 +489,11 @@ final class ProducerFactoryGenerator extends SourceFileGenerator<ProductionBindi return Snippet.format( Joiner.on('\n') .join( - "if (monitor != null) { monitor.methodStarting(); }", + "monitor.methodStarting();", "try {", " return %s;", "} finally {", - " if (monitor != null) { monitor.methodFinished(); }", + " monitor.methodFinished();", "}"), returnSnippet); } diff --git a/compiler/src/main/java/dagger/internal/codegen/ProductionBinding.java b/compiler/src/main/java/dagger/internal/codegen/ProductionBinding.java index 38d45e6a2..1666fbf8c 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ProductionBinding.java +++ b/compiler/src/main/java/dagger/internal/codegen/ProductionBinding.java @@ -20,9 +20,11 @@ import com.google.auto.value.AutoValue; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; -import dagger.producers.Producer; +import dagger.Provides; import dagger.producers.Produces; +import java.util.Set; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; @@ -44,33 +46,26 @@ import static javax.lang.model.element.ElementKind.METHOD; */ @AutoValue abstract class ProductionBinding extends ContributionBinding { + @Override - ImmutableSet<DependencyRequest> implicitDependencies() { - return dependencies(); + Binding.Type bindingType() { + return Binding.Type.PRODUCTION; } - enum Kind { - /** Represents a binding configured by {@link Produces} that doesn't return a future. */ - IMMEDIATE, - /** Represents a binding configured by {@link Produces} that returns a future. */ - FUTURE_PRODUCTION, - /** - * Represents a binding that is not explicitly tied to code, but generated implicitly by the - * framework. - */ - SYNTHETIC_PRODUCTION, - /** - * Represents a binding from a production method on a component dependency that returns a - * future. Methods that return immediate values are considered provision bindings. - */ - COMPONENT_PRODUCTION, + @Override + Provides.Type provisionType() { + return Provides.Type.valueOf(productionType().name()); } - /** - * The type of binding (whether the {@link Produces} method returns a future). For the particular - * type of production, use {@link #productionType}. - */ - abstract Kind bindingKind(); + @Override + Set<DependencyRequest> implicitDependencies() { + // Similar optimizations to ContributionBinding.implicitDependencies(). + if (!monitorRequest().isPresent()) { + return super.implicitDependencies(); + } else { + return Sets.union(monitorRequest().asSet(), super.implicitDependencies()); + } + } /** Returns provision type that was used to bind the key. */ abstract Produces.Type productionType(); @@ -78,40 +73,31 @@ abstract class ProductionBinding extends ContributionBinding { /** Returns the list of types in the throws clause of the method. */ abstract ImmutableList<? extends TypeMirror> thrownTypes(); + /** If this production requires a monitor, this will be the corresponding request. */ + abstract Optional<DependencyRequest> monitorRequest(); + @Override - BindingType bindingType() { + ContributionType contributionType() { switch (productionType()) { case SET: case SET_VALUES: - return BindingType.SET; + return ContributionType.SET; case MAP: - return BindingType.MAP; + return ContributionType.MAP; case UNIQUE: - return BindingType.UNIQUE; + return ContributionType.UNIQUE; default: - throw new IllegalStateException("Unknown production type: " + productionType()); + throw new AssertionError("Unknown production type: " + productionType()); } } - @Override - boolean isSyntheticBinding() { - return bindingKind().equals(Kind.SYNTHETIC_PRODUCTION); - } - - @Override - Class<?> frameworkClass() { - return Producer.class; - } - static final class Factory { private final Types types; private final Key.Factory keyFactory; private final DependencyRequest.Factory dependencyRequestFactory; - Factory(Types types, - Key.Factory keyFactory, - DependencyRequest.Factory - dependencyRequestFactory) { + Factory( + Types types, Key.Factory keyFactory, DependencyRequest.Factory dependencyRequestFactory) { this.types = types; this.keyFactory = keyFactory; this.dependencyRequestFactory = dependencyRequestFactory; @@ -133,6 +119,8 @@ abstract class ProductionBinding extends ContributionBinding { declaredContainer, producesMethod.getParameters(), resolvedMethod.getParameterTypes()); + DependencyRequest monitorRequest = + dependencyRequestFactory.forProductionComponentMonitorProvider(); Kind kind = MoreTypes.isTypeOf(ListenableFuture.class, producesMethod.getReturnType()) ? Kind.FUTURE_PRODUCTION : Kind.IMMEDIATE; @@ -144,27 +132,35 @@ abstract class ProductionBinding extends ContributionBinding { false, ConfigurationAnnotations.getNullableType(producesMethod), Optional.of(MoreTypes.asTypeElement(declaredContainer)), + Optional.<DependencyRequest>absent(), kind, producesAnnotation.type(), - ImmutableList.copyOf(producesMethod.getThrownTypes())); + ImmutableList.copyOf(producesMethod.getThrownTypes()), + Optional.of(monitorRequest)); } - ProductionBinding forImplicitMapBinding(DependencyRequest explicitRequest, - DependencyRequest implicitRequest) { - checkNotNull(explicitRequest); - checkNotNull(implicitRequest); - ImmutableSet<DependencyRequest> dependencies = ImmutableSet.of(implicitRequest); + ProductionBinding implicitMapOfProducerBinding(DependencyRequest mapOfValueRequest) { + checkNotNull(mapOfValueRequest); + Optional<Key> implicitMapOfProducerKey = + keyFactory.implicitMapProducerKeyFrom(mapOfValueRequest.key()); + checkArgument( + implicitMapOfProducerKey.isPresent(), "%s is not for a Map<K, V>", mapOfValueRequest); + DependencyRequest implicitMapOfProducerRequest = + dependencyRequestFactory.forImplicitMapBinding( + mapOfValueRequest, implicitMapOfProducerKey.get()); return new AutoValue_ProductionBinding( - explicitRequest.key(), - implicitRequest.requestElement(), - dependencies, - findBindingPackage(explicitRequest.key()), + mapOfValueRequest.key(), + implicitMapOfProducerRequest.requestElement(), + ImmutableSet.of(implicitMapOfProducerRequest), + findBindingPackage(mapOfValueRequest.key()), false, Optional.<DeclaredType>absent(), Optional.<TypeElement>absent(), - Kind.SYNTHETIC_PRODUCTION, + Optional.<DependencyRequest>absent(), + Kind.SYNTHETIC, Produces.Type.MAP, - ImmutableList.<TypeMirror>of()); + ImmutableList.<TypeMirror>of(), + Optional.<DependencyRequest>absent()); } ProductionBinding forComponentMethod(ExecutableElement componentMethod) { @@ -180,9 +176,11 @@ abstract class ProductionBinding extends ContributionBinding { false, Optional.<DeclaredType>absent(), Optional.<TypeElement>absent(), + Optional.<DependencyRequest>absent(), Kind.COMPONENT_PRODUCTION, Produces.Type.UNIQUE, - ImmutableList.copyOf(componentMethod.getThrownTypes())); + ImmutableList.copyOf(componentMethod.getThrownTypes()), + Optional.<DependencyRequest>absent()); } } } diff --git a/compiler/src/main/java/dagger/internal/codegen/ProductionComponentProcessingStep.java b/compiler/src/main/java/dagger/internal/codegen/ProductionComponentProcessingStep.java index 56f8ccb17..0581b1bb1 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ProductionComponentProcessingStep.java +++ b/compiler/src/main/java/dagger/internal/codegen/ProductionComponentProcessingStep.java @@ -16,10 +16,13 @@ package dagger.internal.codegen; import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; +import com.google.auto.common.MoreElements; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; import com.google.common.collect.SetMultimap; import dagger.producers.ProductionComponent; import java.lang.annotation.Annotation; +import java.util.Map; import java.util.Set; import javax.annotation.processing.Messager; import javax.lang.model.element.Element; @@ -32,11 +35,14 @@ import javax.lang.model.element.TypeElement; * @author Jesse Beder */ final class ProductionComponentProcessingStep extends AbstractComponentProcessingStep { - private final ComponentElementValidator componentElementValidator; + private final Messager messager; + private final ProductionComponentValidator componentValidator; + private final BuilderValidator componentBuilderValidator; ProductionComponentProcessingStep( Messager messager, - final ProductionComponentValidator componentValidator, + ProductionComponentValidator componentValidator, + BuilderValidator componentBuilderValidator, ComponentHierarchyValidator componentHierarchyValidator, BindingGraphValidator bindingGraphValidator, ComponentDescriptor.Factory componentDescriptorFactory, @@ -50,26 +56,48 @@ final class ProductionComponentProcessingStep extends AbstractComponentProcessin componentDescriptorFactory, bindingGraphFactory, componentGenerator); - this.componentElementValidator = - new ComponentElementValidator() { - @Override - boolean validateComponent(TypeElement componentTypeElement, Messager messager) { - ValidationReport<TypeElement> validationReport = - componentValidator.validate(componentTypeElement); - validationReport.printMessagesTo(messager); - return validationReport.isClean(); - } - }; + this.messager = messager; + this.componentValidator = componentValidator; + this.componentBuilderValidator = componentBuilderValidator; } @Override public Set<Class<? extends Annotation>> annotations() { - return ImmutableSet.<Class<? extends Annotation>>of(ProductionComponent.class); + return ImmutableSet.<Class<? extends Annotation>>of( + ProductionComponent.class, ProductionComponent.Builder.class); } + // TODO(beder): Move common logic into the AbstractComponentProcessingStep when implementing + // production subcomponents. @Override protected ComponentElementValidator componentElementValidator( SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { - return componentElementValidator; + final Map<Element, ValidationReport<TypeElement>> builderReportsByComponent = + processComponentBuilders(elementsByAnnotation.get(ProductionComponent.Builder.class)); + return new ComponentElementValidator() { + @Override + boolean validateComponent(TypeElement componentTypeElement, Messager messager) { + ValidationReport<TypeElement> validationReport = + componentValidator.validate(componentTypeElement); + validationReport.printMessagesTo(messager); + if (!validationReport.isClean()) { + return false; + } + ValidationReport<?> builderReport = builderReportsByComponent.get(componentTypeElement); + return builderReport == null || builderReport.isClean(); + } + }; + } + + private Map<Element, ValidationReport<TypeElement>> processComponentBuilders( + Set<? extends Element> componentBuilderElements) { + Map<Element, ValidationReport<TypeElement>> builderReportsByComponent = Maps.newHashMap(); + for (Element element : componentBuilderElements) { + ValidationReport<TypeElement> report = + componentBuilderValidator.validate(MoreElements.asType(element)); + report.printMessagesTo(messager); + builderReportsByComponent.put(element.getEnclosingElement(), report); + } + return builderReportsByComponent; } } diff --git a/compiler/src/main/java/dagger/internal/codegen/ProvisionBinding.java b/compiler/src/main/java/dagger/internal/codegen/ProvisionBinding.java index 8a3c203c8..b2ac74fb3 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ProvisionBinding.java +++ b/compiler/src/main/java/dagger/internal/codegen/ProvisionBinding.java @@ -22,9 +22,7 @@ import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import dagger.Provides; -import java.util.Set; import javax.inject.Inject; -import javax.inject.Provider; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -37,16 +35,15 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import static com.google.auto.common.MoreElements.isAnnotationPresent; +import static com.google.auto.common.MoreTypes.asDeclared; 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.getQualifier; -import static dagger.internal.codegen.ProvisionBinding.Kind.INJECTION; -import static dagger.internal.codegen.ProvisionBinding.Kind.PROVISION; +import static dagger.internal.codegen.Scope.scopeOf; import static javax.lang.model.element.ElementKind.CONSTRUCTOR; import static javax.lang.model.element.ElementKind.FIELD; import static javax.lang.model.element.ElementKind.METHOD; -import static javax.lang.model.element.Modifier.STATIC; /** * A value object representing the mechanism by which a {@link Key} can be provided. New instances @@ -57,91 +54,14 @@ import static javax.lang.model.element.Modifier.STATIC; */ @AutoValue abstract class ProvisionBinding extends ContributionBinding { + @Override - Set<DependencyRequest> implicitDependencies() { - // Optimization: If we don't need the memberInjectionRequest, don't create more objects. - if (!memberInjectionRequest().isPresent()) { - return dependencies(); - } else { - // Optimization: Avoid creating an ImmutableSet+Builder just to union two things together. - return Sets.union(memberInjectionRequest().asSet(), dependencies()); - } - } - - enum Kind { - /** Represents an {@link Inject} binding. */ - INJECTION, - /** Represents a binding configured by {@link Provides}. */ - PROVISION, - /** - * Represents a binding that is not explicitly tied to code, but generated implicitly by the - * framework. - */ - SYNTHETIC_PROVISON, - /** Represents the implicit binding to the component. */ - COMPONENT, - /** Represents a binding from a provision method on a component dependency. */ - COMPONENT_PROVISION, - } - - /** - * The type of binding ({@link Inject} or {@link Provides}). For the particular type of provision, - * use {@link #provisionType}. - */ - abstract Kind bindingKind(); - - /** Returns provision type that was used to bind the key. */ - abstract Provides.Type provisionType(); - - /** - * The scope of the provider. - */ - abstract Scope scope(); - - /** If this provision requires members injection, this will be the corresponding request. */ - abstract Optional<DependencyRequest> memberInjectionRequest(); - - @Override - BindingType bindingType() { - switch (provisionType()) { - case SET: - case SET_VALUES: - return BindingType.SET; - case MAP: - return BindingType.MAP; - case UNIQUE: - return BindingType.UNIQUE; - default: - throw new IllegalStateException("Unknown provision type: " + provisionType()); - } + Binding.Type bindingType() { + return Binding.Type.PROVISION; } - - @Override - boolean isSyntheticBinding() { - return bindingKind().equals(Kind.SYNTHETIC_PROVISON); - } - + @Override - Class<?> frameworkClass() { - return Provider.class; - } - - enum FactoryCreationStrategy { - ENUM_INSTANCE, - CLASS_CONSTRUCTOR, - } - - FactoryCreationStrategy factoryCreationStrategy() { - if (bindingKind().equals(INJECTION) && implicitDependencies().isEmpty()) { - return FactoryCreationStrategy.ENUM_INSTANCE; - } - if (bindingKind().equals(PROVISION) - && implicitDependencies().isEmpty() - && bindingElement().getModifiers().contains(STATIC)) { - return FactoryCreationStrategy.ENUM_INSTANCE; - } - return FactoryCreationStrategy.CLASS_CONSTRUCTOR; - } + abstract Scope scope(); static final class Factory { private final Elements elements; @@ -211,10 +131,10 @@ abstract class ProvisionBinding extends ContributionBinding { hasNonDefaultTypeParameters(bindingTypeElement, key.type(), types), Optional.<DeclaredType>absent(), Optional.<TypeElement>absent(), + membersInjectionRequest, Kind.INJECTION, Provides.Type.UNIQUE, - scope, - membersInjectionRequest); + scope); } private static final ImmutableSet<ElementKind> MEMBER_KINDS = @@ -259,30 +179,35 @@ abstract class ProvisionBinding extends ContributionBinding { false /* no non-default parameter types */, ConfigurationAnnotations.getNullableType(providesMethod), Optional.of(MoreTypes.asTypeElement(declaredContainer)), + Optional.<DependencyRequest>absent(), Kind.PROVISION, providesAnnotation.type(), - scope, - Optional.<DependencyRequest>absent()); + scope); } - ProvisionBinding forImplicitMapBinding(DependencyRequest explicitRequest, - DependencyRequest implicitRequest) { - checkNotNull(explicitRequest); - checkNotNull(implicitRequest); - ImmutableSet<DependencyRequest> dependencies = ImmutableSet.of(implicitRequest); - Scope scope = Scope.scopeOf(implicitRequest.requestElement()); + ProvisionBinding implicitMapOfProviderBinding(DependencyRequest mapOfValueRequest) { + checkNotNull(mapOfValueRequest); + Optional<Key> implicitMapOfProviderKey = + keyFactory.implicitMapProviderKeyFrom(mapOfValueRequest.key()); + checkArgument( + implicitMapOfProviderKey.isPresent(), + "%s is not a request for Map<K, V>", + mapOfValueRequest); + DependencyRequest implicitMapOfProviderRequest = + dependencyRequestFactory.forImplicitMapBinding( + mapOfValueRequest, implicitMapOfProviderKey.get()); return new AutoValue_ProvisionBinding( - explicitRequest.key(), - implicitRequest.requestElement(), - dependencies, - findBindingPackage(explicitRequest.key()), + mapOfValueRequest.key(), + implicitMapOfProviderRequest.requestElement(), + ImmutableSet.of(implicitMapOfProviderRequest), + findBindingPackage(mapOfValueRequest.key()), false /* no non-default parameter types */, Optional.<DeclaredType>absent(), Optional.<TypeElement>absent(), - Kind.SYNTHETIC_PROVISON, + Optional.<DependencyRequest>absent(), + Kind.SYNTHETIC, Provides.Type.MAP, - scope, - Optional.<DependencyRequest>absent()); + scopeOf(implicitMapOfProviderRequest.requestElement())); } ProvisionBinding forComponent(TypeElement componentDefinitionType) { @@ -295,10 +220,10 @@ abstract class ProvisionBinding extends ContributionBinding { false /* no non-default parameter types */, Optional.<DeclaredType>absent(), Optional.<TypeElement>absent(), + Optional.<DependencyRequest>absent(), Kind.COMPONENT, Provides.Type.UNIQUE, - Scope.unscoped(), - Optional.<DependencyRequest>absent()); + Scope.unscoped()); } ProvisionBinding forComponentMethod(ExecutableElement componentMethod) { @@ -314,10 +239,30 @@ abstract class ProvisionBinding extends ContributionBinding { false /* no non-default parameter types */, ConfigurationAnnotations.getNullableType(componentMethod), Optional.<TypeElement>absent(), + Optional.<DependencyRequest>absent(), Kind.COMPONENT_PROVISION, Provides.Type.UNIQUE, - scope, - Optional.<DependencyRequest>absent()); + scope); + } + + ProvisionBinding forSubcomponentBuilderMethod( + ExecutableElement subcomponentBuilderMethod, TypeElement contributedBy) { + checkNotNull(subcomponentBuilderMethod); + checkArgument(subcomponentBuilderMethod.getKind().equals(METHOD)); + checkArgument(subcomponentBuilderMethod.getParameters().isEmpty()); + DeclaredType declaredContainer = asDeclared(contributedBy.asType()); + return new AutoValue_ProvisionBinding( + keyFactory.forSubcomponentBuilderMethod(subcomponentBuilderMethod, declaredContainer), + subcomponentBuilderMethod, + ImmutableSet.<DependencyRequest>of(), + Optional.<String>absent(), + false /* no non-default parameter types */, + Optional.<DeclaredType>absent(), + Optional.of(contributedBy), + Optional.<DependencyRequest>absent(), + Kind.SUBCOMPONENT_BUILDER, + Provides.Type.UNIQUE, + Scope.unscoped()); } } } diff --git a/compiler/src/main/java/dagger/internal/codegen/ProvisionBindingFormatter.java b/compiler/src/main/java/dagger/internal/codegen/ProvisionBindingFormatter.java deleted file mode 100644 index 92d031042..000000000 --- a/compiler/src/main/java/dagger/internal/codegen/ProvisionBindingFormatter.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2014 Google, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dagger.internal.codegen; - -import com.google.common.base.Optional; - -import static com.google.auto.common.MoreElements.asExecutable; -import static com.google.auto.common.MoreTypes.asDeclared; - -/** - * Formats a {@link ProvisionBinding} into a {@link String} suitable for use in error messages. - * - * @author Christian Gruber - * @since 2.0 - */ -final class ProvisionBindingFormatter extends Formatter<ProvisionBinding> { - private final MethodSignatureFormatter methodSignatureFormatter; - - ProvisionBindingFormatter(MethodSignatureFormatter methodSignatureFormatter) { - this.methodSignatureFormatter = methodSignatureFormatter; - } - - @Override public String format(ProvisionBinding binding) { - switch (binding.bindingKind()) { - case PROVISION: - return methodSignatureFormatter.format(asExecutable(binding.bindingElement()), - Optional.of(asDeclared(binding.contributedBy().get().asType()))); - case COMPONENT_PROVISION: - return methodSignatureFormatter.format(asExecutable(binding.bindingElement())); - default: - throw new UnsupportedOperationException( - "Not yet supporting " + binding.bindingKind() + " binding types."); - } - } -} diff --git a/compiler/src/main/java/dagger/internal/codegen/ResolvedBindings.java b/compiler/src/main/java/dagger/internal/codegen/ResolvedBindings.java index 7ef4deb92..024097ef0 100644 --- a/compiler/src/main/java/dagger/internal/codegen/ResolvedBindings.java +++ b/compiler/src/main/java/dagger/internal/codegen/ResolvedBindings.java @@ -16,13 +16,17 @@ package dagger.internal.codegen; import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; -import java.util.Set; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; -import static dagger.internal.codegen.ContributionBinding.bindingTypeFor; +import static dagger.internal.codegen.ContributionBinding.contributionTypeFor; /** * The collection of bindings that have been resolved for a binding key. @@ -31,63 +35,168 @@ import static dagger.internal.codegen.ContributionBinding.bindingTypeFor; */ @AutoValue abstract class ResolvedBindings { + /** + * The binding key for which the {@link #bindings()} have been resolved. + */ abstract BindingKey bindingKey(); + + /** + * The component in which the bindings in {@link #ownedBindings()}, + * {@link #ownedContributionBindings()}, and {@link #ownedMembersInjectionBinding()} were + * resolved. + */ abstract ComponentDescriptor owningComponent(); - abstract ImmutableSet<? extends Binding> ownedBindings(); - abstract ImmutableSetMultimap<ComponentDescriptor, ? extends Binding> inheritedBindings(); - static ResolvedBindings create( - BindingKey bindingKey, - ComponentDescriptor owningComponent, - Set<? extends Binding> ownedBindings, - Multimap<ComponentDescriptor, ? extends Binding> inheritedBindings) { - return new AutoValue_ResolvedBindings( - bindingKey, - owningComponent, - ImmutableSet.copyOf(ownedBindings), - ImmutableSetMultimap.copyOf(inheritedBindings)); + /** + * The contribution bindings for {@link #bindingKey()} that were resolved in + * {@link #owningComponent()} or its ancestor components, keyed by the component in which the + * binding was resolved. If {@link #bindingKey()}'s kind is not + * {@link BindingKey.Kind#CONTRIBUTION}, this is empty. + */ + abstract ImmutableSetMultimap<ComponentDescriptor, ContributionBinding> allContributionBindings(); + + /** + * The members-injection bindings for {@link #bindingKey()} that were resolved in + * {@link #owningComponent()} or its ancestor components, keyed by the component in which the + * binding was resolved. If {@link #bindingKey()}'s kind is not + * {@link BindingKey.Kind#MEMBERS_INJECTION}, this is empty. + */ + abstract ImmutableMap<ComponentDescriptor, MembersInjectionBinding> allMembersInjectionBindings(); + + /** + * All bindings for {@link #bindingKey()}, regardless of in which component they were resolved. + */ + ImmutableSet<? extends Binding> bindings() { + switch (bindingKey().kind()) { + case CONTRIBUTION: + return contributionBindings(); + + case MEMBERS_INJECTION: + return ImmutableSet.copyOf(membersInjectionBinding().asSet()); + + default: + throw new AssertionError(bindingKey()); + } + } + + /** + * All bindings for {@link #bindingKey()} that were resolved in {@link #owningComponent()}. + */ + ImmutableSet<? extends Binding> ownedBindings() { + switch (bindingKey().kind()) { + case CONTRIBUTION: + return ownedContributionBindings(); + + case MEMBERS_INJECTION: + return ImmutableSet.copyOf(ownedMembersInjectionBinding().asSet()); + + default: + throw new AssertionError(bindingKey()); + } + } + + /** + * All contribution bindings, regardless of owning component. + * + * @throws IllegalStateException if {@link #bindingKey()} is not a + * {@link BindingKey.Kind#CONTRIBUTION}. + */ + ImmutableSet<ContributionBinding> contributionBindings() { + checkState(bindingKey().kind().equals(BindingKey.Kind.CONTRIBUTION)); + return ImmutableSet.copyOf(allContributionBindings().values()); + } + + /** + * The contribution bindings that were resolved in {@link #owningComponent()}. + * + * @throws IllegalStateException if {@link #bindingKey()} is not a + * {@link BindingKey.Kind#CONTRIBUTION}. + */ + ImmutableSet<ContributionBinding> ownedContributionBindings() { + checkState(bindingKey().kind().equals(BindingKey.Kind.CONTRIBUTION)); + return allContributionBindings().get(owningComponent()); + } + + /** + * The members-injection binding, regardless of owning component. + * + * @throws IllegalStateException if {@link #bindingKey()} is not a + * {@link BindingKey.Kind#MEMBERS_INJECTION}. + */ + Optional<MembersInjectionBinding> membersInjectionBinding() { + checkState(bindingKey().kind().equals(BindingKey.Kind.MEMBERS_INJECTION)); + ImmutableSet<MembersInjectionBinding> membersInjectionBindings = + FluentIterable.from(allMembersInjectionBindings().values()).toSet(); + return membersInjectionBindings.isEmpty() + ? Optional.<MembersInjectionBinding>absent() + : Optional.of(Iterables.getOnlyElement(membersInjectionBindings)); + } + + /** + * The members-injection binding that was resolved in {@link #owningComponent()}. + * + * @throws IllegalStateException if {@link #bindingKey()} is not a + * {@link BindingKey.Kind#MEMBERS_INJECTION}. + */ + Optional<MembersInjectionBinding> ownedMembersInjectionBinding() { + checkState(bindingKey().kind().equals(BindingKey.Kind.MEMBERS_INJECTION)); + return Optional.fromNullable(allMembersInjectionBindings().get(owningComponent())); } - static ResolvedBindings create( + /** + * Creates a {@link ResolvedBindings} for contribution bindings. + */ + static ResolvedBindings forContributionBindings( BindingKey bindingKey, ComponentDescriptor owningComponent, - Binding... ownedBindings) { + Multimap<ComponentDescriptor, ? extends ContributionBinding> contributionBindings) { + checkArgument(bindingKey.kind().equals(BindingKey.Kind.CONTRIBUTION)); return new AutoValue_ResolvedBindings( bindingKey, owningComponent, - ImmutableSet.copyOf(ownedBindings), - ImmutableSetMultimap.<ComponentDescriptor, Binding>of()); - } - - ImmutableSet<? extends Binding> bindings() { - return new ImmutableSet.Builder<Binding>() - .addAll(ownedBindings()) - .addAll(inheritedBindings().values()) - .build(); + ImmutableSetMultimap.<ComponentDescriptor, ContributionBinding>copyOf(contributionBindings), + ImmutableMap.<ComponentDescriptor, MembersInjectionBinding>of()); } - @SuppressWarnings("unchecked") // checked by validator - ImmutableSet<? extends ContributionBinding> ownedContributionBindings() { - checkState(bindingKey().kind().equals(BindingKey.Kind.CONTRIBUTION)); - return (ImmutableSet<? extends ContributionBinding>) ownedBindings(); + /** + * Creates a {@link ResolvedBindings} for contribution bindings. + */ + static ResolvedBindings forContributionBindings( + BindingKey bindingKey, + ComponentDescriptor owningComponent, + ContributionBinding... ownedContributionBindings) { + return forContributionBindings( + bindingKey, + owningComponent, + ImmutableSetMultimap.<ComponentDescriptor, ContributionBinding>builder() + .putAll(owningComponent, ownedContributionBindings) + .build()); } - @SuppressWarnings("unchecked") // checked by validator - ImmutableSet<? extends ContributionBinding> contributionBindings() { - checkState(bindingKey().kind().equals(BindingKey.Kind.CONTRIBUTION)); - return new ImmutableSet.Builder<ContributionBinding>() - .addAll((Iterable<? extends ContributionBinding>) ownedBindings()) - .addAll((Iterable<? extends ContributionBinding>) inheritedBindings().values()) - .build(); + /** + * Creates a {@link ResolvedBindings} for members injection bindings. + */ + static ResolvedBindings forMembersInjectionBinding( + BindingKey bindingKey, + ComponentDescriptor owningComponent, + MembersInjectionBinding ownedMembersInjectionBinding) { + checkArgument(bindingKey.kind().equals(BindingKey.Kind.MEMBERS_INJECTION)); + return new AutoValue_ResolvedBindings( + bindingKey, + owningComponent, + ImmutableSetMultimap.<ComponentDescriptor, ContributionBinding>of(), + ImmutableMap.of(owningComponent, ownedMembersInjectionBinding)); } - @SuppressWarnings("unchecked") // checked by validator - ImmutableSet<? extends MembersInjectionBinding> membersInjectionBindings() { - checkState(bindingKey().kind().equals(BindingKey.Kind.MEMBERS_INJECTION)); - return new ImmutableSet.Builder<MembersInjectionBinding>() - .addAll((Iterable<? extends MembersInjectionBinding>) ownedBindings()) - .addAll((Iterable<? extends MembersInjectionBinding>) inheritedBindings().values()) - .build(); + /** + * Creates a {@link ResolvedBindings} appropriate for when there are no bindings for the key. + */ + static ResolvedBindings noBindings(BindingKey bindingKey, ComponentDescriptor owningComponent) { + return new AutoValue_ResolvedBindings( + bindingKey, + owningComponent, + ImmutableSetMultimap.<ComponentDescriptor, ContributionBinding>of(), + ImmutableMap.<ComponentDescriptor, MembersInjectionBinding>of()); } /** @@ -95,14 +204,8 @@ abstract class ResolvedBindings { * as this one, but no {@link #ownedBindings()}. */ ResolvedBindings asInheritedIn(ComponentDescriptor owningComponent) { - return ResolvedBindings.create( - bindingKey(), - owningComponent, - ImmutableSet.<Binding>of(), - new ImmutableSetMultimap.Builder<ComponentDescriptor, Binding>() - .putAll(inheritedBindings()) - .putAll(owningComponent, ownedBindings()) - .build()); + return new AutoValue_ResolvedBindings( + bindingKey(), owningComponent, allContributionBindings(), allMembersInjectionBindings()); } /** @@ -111,7 +214,7 @@ abstract class ResolvedBindings { boolean isMultibindings() { return bindingKey().kind().equals(BindingKey.Kind.CONTRIBUTION) && !contributionBindings().isEmpty() - && bindingTypeFor(contributionBindings()).isMultibinding(); + && contributionTypeFor(contributionBindings()).isMultibinding(); } /** @@ -120,6 +223,6 @@ abstract class ResolvedBindings { boolean isUniqueContribution() { return bindingKey().kind().equals(BindingKey.Kind.CONTRIBUTION) && !contributionBindings().isEmpty() - && !bindingTypeFor(contributionBindings()).isMultibinding(); + && !contributionTypeFor(contributionBindings()).isMultibinding(); } } diff --git a/compiler/src/main/java/dagger/internal/codegen/SourceFiles.java b/compiler/src/main/java/dagger/internal/codegen/SourceFiles.java index 890d3553b..7ad0acbc4 100644 --- a/compiler/src/main/java/dagger/internal/codegen/SourceFiles.java +++ b/compiler/src/main/java/dagger/internal/codegen/SourceFiles.java @@ -23,7 +23,6 @@ import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import dagger.internal.DoubleCheckLazy; -import dagger.internal.codegen.ContributionBinding.BindingType; import dagger.internal.codegen.writer.ClassName; import dagger.internal.codegen.writer.ParameterizedTypeName; import dagger.internal.codegen.writer.Snippet; @@ -31,7 +30,6 @@ import dagger.internal.codegen.writer.TypeName; import dagger.internal.codegen.writer.TypeNames; import java.util.Collection; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.lang.model.element.ExecutableElement; @@ -40,6 +38,7 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import static com.google.common.base.CaseFormat.UPPER_CAMEL; +import static com.google.common.base.Preconditions.checkArgument; /** * Utilities for generating files. @@ -184,83 +183,96 @@ class SourceFiles { throw new AssertionError(); } } - - static ClassName factoryNameForProvisionBinding(ProvisionBinding binding) { - TypeElement enclosingTypeElement = binding.bindingTypeElement(); - ClassName enclosingClassName = ClassName.fromTypeElement(enclosingTypeElement); - switch (binding.bindingKind()) { - case INJECTION: + + /** + * Returns the generated factory or members injector name for a binding. + */ + static ClassName generatedClassNameForBinding(Binding binding) { + switch (binding.bindingType()) { case PROVISION: - return enclosingClassName.topLevelClassName().peerNamed( - enclosingClassName.classFileName() + "_" + factoryPrefix(binding) + "Factory"); - case SYNTHETIC_PROVISON: - throw new IllegalArgumentException(); + case PRODUCTION: + ContributionBinding contribution = (ContributionBinding) binding; + checkArgument(!contribution.isSyntheticBinding()); + ClassName enclosingClassName = ClassName.fromTypeElement(contribution.bindingTypeElement()); + switch (contribution.bindingKind()) { + case INJECTION: + case PROVISION: + case IMMEDIATE: + case FUTURE_PRODUCTION: + return enclosingClassName + .topLevelClassName() + .peerNamed( + enclosingClassName.classFileName() + + "_" + + factoryPrefix(contribution) + + "Factory"); + + default: + throw new AssertionError(); + } + + case MEMBERS_INJECTION: + return membersInjectorNameForType(binding.bindingTypeElement()); + default: throw new AssertionError(); } } /** - * Returns the factory name parameterized with the ProvisionBinding's parameters (if necessary). + * Returns the generated factory or members injector name parameterized with the proper type + * parameters if necessary. */ - static TypeName parameterizedFactoryNameForProvisionBinding( - ProvisionBinding binding) { - ClassName factoryName = factoryNameForProvisionBinding(binding); - List<TypeName> parameters = ImmutableList.of(); - if (binding.bindingType().equals(BindingType.UNIQUE)) { - switch(binding.bindingKind()) { - case INJECTION: - TypeName bindingName = TypeNames.forTypeMirror(binding.key().type()); - // If the binding is parameterized, parameterize the factory. - if (bindingName instanceof ParameterizedTypeName) { - parameters = ((ParameterizedTypeName) bindingName).parameters(); - } - break; - case PROVISION: - // For provision bindings, we parameterize creation on the types of - // the module, not the types of the binding. - // Consider: Module<A, B, C> { @Provides List<B> provideB(B b) { .. }} - // The binding is just parameterized on <B>, but we need all of <A, B, C>. - if (!binding.bindingTypeElement().getTypeParameters().isEmpty()) { - parameters = ((ParameterizedTypeName) TypeNames.forTypeMirror( - binding.bindingTypeElement().asType())).parameters(); - } - break; - default: // fall through. - } - } - return parameters.isEmpty() ? factoryName - : ParameterizedTypeName.create(factoryName, parameters); + static TypeName parameterizedGeneratedTypeNameForBinding(Binding binding) { + return generatedClassNameForBinding(binding).withTypeParameters(bindingTypeParameters(binding)); } + + private static ImmutableList<TypeName> bindingTypeParameters(Binding binding) + throws AssertionError { + TypeMirror bindingType; + switch (binding.bindingType()) { + case PROVISION: + case PRODUCTION: + ContributionBinding contributionBinding = (ContributionBinding) binding; + if (contributionBinding.contributionType().isMultibinding()) { + return ImmutableList.of(); + } + switch (contributionBinding.bindingKind()) { + case INJECTION: + bindingType = contributionBinding.key().type(); + break; + + case PROVISION: + // For provision bindings, we parameterize creation on the types of + // the module, not the types of the binding. + // Consider: Module<A, B, C> { @Provides List<B> provideB(B b) { .. }} + // The binding is just parameterized on <B>, but we need all of <A, B, C>. + bindingType = contributionBinding.bindingTypeElement().asType(); + break; + + case IMMEDIATE: + case FUTURE_PRODUCTION: + // TODO(beder): Can these be treated just like PROVISION? + throw new UnsupportedOperationException(); + + default: + return ImmutableList.of(); + } + break; + + case MEMBERS_INJECTION: + bindingType = binding.key().type(); + break; - static ClassName factoryNameForProductionBinding(ProductionBinding binding) { - TypeElement enclosingTypeElement = binding.bindingTypeElement(); - ClassName enclosingClassName = ClassName.fromTypeElement(enclosingTypeElement); - switch (binding.bindingKind()) { - case IMMEDIATE: - case FUTURE_PRODUCTION: - return enclosingClassName.topLevelClassName().peerNamed( - enclosingClassName.classFileName() + "_" + factoryPrefix(binding) + "Factory"); default: throw new AssertionError(); } + TypeName bindingTypeName = TypeNames.forTypeMirror(bindingType); + return bindingTypeName instanceof ParameterizedTypeName + ? ((ParameterizedTypeName) bindingTypeName).parameters() + : ImmutableList.<TypeName>of(); } - - /** - * Returns the members injector's name parameterized with the binding's parameters (if necessary). - */ - static TypeName parameterizedMembersInjectorNameForMembersInjectionBinding( - MembersInjectionBinding binding) { - ClassName factoryName = membersInjectorNameForType(binding.bindingElement()); - TypeName bindingName = TypeNames.forTypeMirror(binding.key().type()); - // If the binding is parameterized, parameterize the MembersInjector. - if (bindingName instanceof ParameterizedTypeName) { - return ParameterizedTypeName.create(factoryName, - ((ParameterizedTypeName) bindingName).parameters()); - } - return factoryName; - } - + static ClassName membersInjectorNameForType(TypeElement typeElement) { ClassName injectedClassName = ClassName.fromTypeElement(typeElement); return injectedClassName @@ -268,24 +280,24 @@ class SourceFiles { .peerNamed(injectedClassName.classFileName() + "_MembersInjector"); } - private static String factoryPrefix(ProvisionBinding binding) { + static ClassName generatedMonitoringModuleName(TypeElement componentElement) { + ClassName componentName = ClassName.fromTypeElement(componentElement); + return componentName + .topLevelClassName() + .peerNamed(componentName.classFileName() + "_MonitoringModule"); + } + + private static String factoryPrefix(ContributionBinding binding) { switch (binding.bindingKind()) { case INJECTION: return ""; - case PROVISION: - return CaseFormat.LOWER_CAMEL.to(UPPER_CAMEL, - ((ExecutableElement) binding.bindingElement()).getSimpleName().toString()); - default: - throw new IllegalArgumentException(); - } - } - private static String factoryPrefix(ProductionBinding binding) { - switch (binding.bindingKind()) { + case PROVISION: case IMMEDIATE: case FUTURE_PRODUCTION: - return CaseFormat.LOWER_CAMEL.to(UPPER_CAMEL, - ((ExecutableElement) binding.bindingElement()).getSimpleName().toString()); + return CaseFormat.LOWER_CAMEL.to( + UPPER_CAMEL, ((ExecutableElement) binding.bindingElement()).getSimpleName().toString()); + default: throw new IllegalArgumentException(); } diff --git a/compiler/src/main/java/dagger/internal/codegen/SubcomponentWriter.java b/compiler/src/main/java/dagger/internal/codegen/SubcomponentWriter.java index 8cb31b92d..128766872 100644 --- a/compiler/src/main/java/dagger/internal/codegen/SubcomponentWriter.java +++ b/compiler/src/main/java/dagger/internal/codegen/SubcomponentWriter.java @@ -18,10 +18,7 @@ package dagger.internal.codegen; import com.google.auto.common.MoreTypes; import com.google.common.base.CaseFormat; import com.google.common.base.Optional; -import com.google.common.base.Predicates; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import dagger.internal.codegen.ComponentDescriptor.BuilderSpec; import dagger.internal.codegen.ComponentGenerator.MemberSelect; import dagger.internal.codegen.writer.ClassName; @@ -32,6 +29,7 @@ import dagger.internal.codegen.writer.Snippet; import dagger.internal.codegen.writer.TypeName; import dagger.internal.codegen.writer.TypeNames; 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; @@ -40,6 +38,7 @@ import javax.lang.model.type.TypeMirror; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.Verify.verify; +import static com.google.common.collect.Sets.difference; import static dagger.internal.codegen.AbstractComponentWriter.InitializationState.UNINITIALIZED; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; @@ -189,12 +188,9 @@ class SubcomponentWriter extends AbstractComponentWriter { } } - ImmutableSet<TypeElement> uninitializedModules = - FluentIterable.from(graph.componentDescriptor().transitiveModules()) - .transform(ModuleDescriptor.getModuleElement()) - .filter(Predicates.not(Predicates.in(componentContributionFields.keySet()))) - .toSet(); - + Set<TypeElement> uninitializedModules = + difference(graph.componentRequirements(), componentContributionFields.keySet()); + for (TypeElement moduleType : uninitializedModules) { String preferredModuleName = CaseFormat.UPPER_CAMEL.to(LOWER_CAMEL, moduleType.getSimpleName().toString()); diff --git a/compiler/src/main/java/dagger/internal/codegen/Util.java b/compiler/src/main/java/dagger/internal/codegen/Util.java index faa0459c4..8c1aba399 100644 --- a/compiler/src/main/java/dagger/internal/codegen/Util.java +++ b/compiler/src/main/java/dagger/internal/codegen/Util.java @@ -21,7 +21,7 @@ import com.google.common.base.Equivalence; import com.google.common.base.Equivalence.Wrapper; import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; -import java.util.List; +import dagger.producers.Produced; import java.util.Map; import java.util.Set; import javax.inject.Provider; @@ -46,31 +46,29 @@ import static javax.lang.model.element.Modifier.STATIC; */ final class Util { /** - * Returns the {@code V} type for a {@link Map} type like Map<K, Provider<V>>} if the map + * Returns the {@code V} type for a {@link Map} type like {@code Map<K, Provider<V>>} if the map * includes such a construction */ - public static DeclaredType getProvidedValueTypeOfMap(DeclaredType mapType) { + public static TypeMirror getProvidedValueTypeOfMap(DeclaredType mapType) { checkState(MoreTypes.isTypeOf(Map.class, mapType), "%s is not a Map.", mapType); - return asDeclared(asDeclared(mapType.getTypeArguments().get(1)).getTypeArguments().get(0)); + return asDeclared(mapType.getTypeArguments().get(1)).getTypeArguments().get(0); } // TODO(cgruber): Consider an object that holds and exposes the various parts of a Map type. /** * returns the value type for a {@link Map} type like Map<K, V>}. */ - public static DeclaredType getValueTypeOfMap(DeclaredType mapType) { + public static TypeMirror getValueTypeOfMap(DeclaredType mapType) { checkState(MoreTypes.isTypeOf(Map.class, mapType), "%s is not a Map.", mapType); - List<? extends TypeMirror> mapArgs = mapType.getTypeArguments(); - return asDeclared(mapArgs.get(1)); + return mapType.getTypeArguments().get(1); } /** * Returns the key type for a {@link Map} type like Map<K, Provider<V>>} */ - public static DeclaredType getKeyTypeOfMap(DeclaredType mapType) { + public static TypeMirror getKeyTypeOfMap(DeclaredType mapType) { checkState(MoreTypes.isTypeOf(Map.class, mapType), "%s is not a Map.", mapType); - List<? extends TypeMirror> mapArgs = mapType.getTypeArguments(); - return MoreTypes.asDeclared(mapArgs.get(0)); + return mapType.getTypeArguments().get(0); } /** @@ -91,6 +89,13 @@ final class Util { && MoreTypes.isTypeOf(Provider.class, asDeclared(type).getTypeArguments().get(1)); } + /** Returns true if {@code type} is a {@code Set<Produced<T>>}. */ + static boolean isSetOfProduced(TypeMirror type) { + return MoreTypes.isType(type) + && MoreTypes.isTypeOf(Set.class, type) + && MoreTypes.isTypeOf(Produced.class, MoreTypes.asDeclared(type).getTypeArguments().get(0)); + } + /** * Wraps an {@link Optional} of a type in an {@code Optional} of a {@link Wrapper} for that type. */ diff --git a/compiler/src/main/java/dagger/internal/codegen/writer/AnnotationWriter.java b/compiler/src/main/java/dagger/internal/codegen/writer/AnnotationWriter.java index b2cb45134..8dbf27bc3 100644 --- a/compiler/src/main/java/dagger/internal/codegen/writer/AnnotationWriter.java +++ b/compiler/src/main/java/dagger/internal/codegen/writer/AnnotationWriter.java @@ -15,9 +15,10 @@ */ package dagger.internal.codegen.writer; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.FluentIterable; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import java.io.IOException; import java.util.Map.Entry; import java.util.Set; @@ -27,6 +28,7 @@ import static dagger.internal.codegen.writer.Writables.toStringWritable; public final class AnnotationWriter implements Writable, HasClassReferences { private final ClassName annotationName; + private final Set<HasClassReferences> memberReferences = Sets.newLinkedHashSet(); private final SortedMap<String, Writable> memberMap = Maps.newTreeMap(); AnnotationWriter(ClassName annotationName) { @@ -45,6 +47,12 @@ public final class AnnotationWriter implements Writable, HasClassReferences { memberMap.put(name, toStringWritable(StringLiteral.forValue(value))); } + public <T extends Enum<T>> void setMember(String name, T value) { + Snippet snippet = Snippet.format("%s.%s", ClassName.fromClass(value.getClass()), value); + memberMap.put(name, snippet); + memberReferences.add(snippet); + } + @Override public Appendable write(Appendable appendable, Context context) throws IOException { appendable.append('@'); @@ -65,6 +73,9 @@ public final class AnnotationWriter implements Writable, HasClassReferences { @Override public Set<ClassName> referencedClasses() { - return ImmutableSet.of(annotationName); + return FluentIterable.from(memberReferences) + .append(annotationName) + .transformAndConcat(HasClassReferences.COMBINER) + .toSet(); } } diff --git a/compiler/src/main/java/dagger/internal/codegen/writer/ClassName.java b/compiler/src/main/java/dagger/internal/codegen/writer/ClassName.java index 5b7776853..bd0791fc6 100644 --- a/compiler/src/main/java/dagger/internal/codegen/writer/ClassName.java +++ b/compiler/src/main/java/dagger/internal/codegen/writer/ClassName.java @@ -127,7 +127,6 @@ public final class ClassName implements TypeName, Comparable<ClassName> { public ClassName nestedClassNamed(String memberClassName) { checkNotNull(memberClassName); checkArgument(SourceVersion.isIdentifier(memberClassName)); - checkArgument(Ascii.isUpperCase(memberClassName.charAt(0))); return new ClassName(packageName(), new ImmutableList.Builder<String>() .addAll(enclosingSimpleNames()) @@ -139,10 +138,17 @@ public final class ClassName implements TypeName, Comparable<ClassName> { public ClassName peerNamed(String peerClassName) { checkNotNull(peerClassName); checkArgument(SourceVersion.isIdentifier(peerClassName)); - checkArgument(Ascii.isUpperCase(peerClassName.charAt(0))); return new ClassName(packageName(), enclosingSimpleNames(), peerClassName); } + /** + * Returns a parameterized type name with this as its raw type if {@code parameters} is not empty. + * If {@code parameters} is empty, returns this object. + */ + public TypeName withTypeParameters(List<? extends TypeName> parameters) { + return parameters.isEmpty() ? this : ParameterizedTypeName.create(this, parameters); + } + private static final ImmutableSet<NestingKind> ACCEPTABLE_NESTING_KINDS = Sets.immutableEnumSet(TOP_LEVEL, MEMBER); diff --git a/compiler/src/main/java/dagger/internal/codegen/writer/Snippet.java b/compiler/src/main/java/dagger/internal/codegen/writer/Snippet.java index 2d11f60f7..80ab944f8 100644 --- a/compiler/src/main/java/dagger/internal/codegen/writer/Snippet.java +++ b/compiler/src/main/java/dagger/internal/codegen/writer/Snippet.java @@ -27,28 +27,9 @@ import java.util.Formatter; import java.util.Iterator; import java.util.Set; -public final class Snippet implements HasClassReferences, Writable { - private final String format; - private final ImmutableSet<TypeName> types; - private final ImmutableList<Object> args; - - private Snippet(String format, ImmutableSet<TypeName> types, ImmutableList<Object> args) { - this.format = format; - this.types = types; - this.args = args; - } - - public String format() { - return format; - } +public abstract class Snippet implements HasClassReferences, Writable { - public ImmutableList<Object> args() { - return args; - } - - public ImmutableSet<TypeName> types() { - return types; - } + abstract ImmutableSet<TypeName> types(); @Override public String toString() { @@ -56,40 +37,96 @@ public final class Snippet implements HasClassReferences, Writable { } @Override - public Set<ClassName> referencedClasses() { - return FluentIterable.from(types) - .transformAndConcat(new Function<TypeName, Set<ClassName>>() { - @Override - public Set<ClassName> apply(TypeName input) { - return input.referencedClasses(); - } - }) + public final Set<ClassName> referencedClasses() { + return FluentIterable.from(types()) + .transformAndConcat( + new Function<TypeName, Set<ClassName>>() { + @Override + public Set<ClassName> apply(TypeName input) { + return input.referencedClasses(); + } + }) .toSet(); } - @Override - public Appendable write(Appendable appendable, Context context) throws IOException { - ImmutableList.Builder<Object> formattedArgsBuilder = ImmutableList.builder(); - for (Object arg : args) { - if (arg instanceof Writable) { - formattedArgsBuilder.add(((Writable) arg).write(new StringBuilder(), context).toString()); - } else { - formattedArgsBuilder.add(arg); + private static final class BasicSnippet extends Snippet { + final String format; + final ImmutableSet<TypeName> types; + final ImmutableList<Object> args; + + BasicSnippet(String format, ImmutableSet<TypeName> types, ImmutableList<Object> args) { + this.format = format; + this.types = types; + this.args = args; + } + + @Override + ImmutableSet<TypeName> types() { + return types; + } + + @Override + public Appendable write(Appendable appendable, Context context) throws IOException { + ImmutableList.Builder<Object> formattedArgsBuilder = ImmutableList.builder(); + for (Object arg : args) { + if (arg instanceof Writable) { + formattedArgsBuilder.add(((Writable) arg).write(new StringBuilder(), context).toString()); + } else { + formattedArgsBuilder.add(arg); + } } + + @SuppressWarnings("resource") // intentionally don't close the formatter + Formatter formatter = new Formatter(appendable); + formatter.format(format, Iterables.toArray(formattedArgsBuilder.build(), Object.class)); + + return appendable; + } + } + + private static final class CompoundSnippet extends Snippet { + final String joinToken; + final ImmutableList<Snippet> snippets; + + CompoundSnippet(String joinToken, ImmutableList<Snippet> snippets) { + this.joinToken = joinToken; + this.snippets = snippets; } - @SuppressWarnings("resource") // intentionally don't close the formatter - Formatter formatter = new Formatter(appendable); - formatter.format(format, formattedArgsBuilder.build().toArray(new Object[0])); + @Override + ImmutableSet<TypeName> types() { + return FluentIterable.from(snippets) + .transformAndConcat( + new Function<Snippet, Iterable<TypeName>>() { + @Override + public Iterable<TypeName> apply(Snippet input) { + return input.types(); + } + }) + .toSet(); + } - return appendable; + @Override + public Appendable write(Appendable appendable, Context context) throws IOException { + Iterator<Snippet> snippetIterator = snippets.iterator(); + if (snippetIterator.hasNext()) { + Snippet firstSnippet = snippetIterator.next(); + firstSnippet.write(appendable, context); + while (snippetIterator.hasNext()) { + Snippet nextSnippet = snippetIterator.next(); + appendable.append(joinToken); + nextSnippet.write(appendable, context); + } + } + return appendable; + } } public static Snippet format(String format, Object... args) { ImmutableSet.Builder<TypeName> types = ImmutableSet.builder(); for (Object arg : args) { if (arg instanceof Snippet) { - types.addAll(((Snippet) arg).types); + types.addAll(((Snippet) arg).types()); } if (arg instanceof TypeName) { types.add((TypeName) arg); @@ -98,7 +135,7 @@ public final class Snippet implements HasClassReferences, Writable { types.add(((HasTypeName) arg).name()); } } - return new Snippet(format, types.build(), ImmutableList.copyOf(args)); + return new BasicSnippet(format, types.build(), ImmutableList.copyOf(args)); } public static Snippet format(String format, Iterable<? extends Object> args) { @@ -121,66 +158,20 @@ public final class Snippet implements HasClassReferences, Writable { } public static Snippet makeParametersSnippet(Iterable<Snippet> parameterSnippets) { - Iterator<Snippet> iterator = parameterSnippets.iterator(); - StringBuilder stringBuilder = new StringBuilder(); - ImmutableSet.Builder<TypeName> typesBuilder = ImmutableSet.builder(); - ImmutableList.Builder<Object> argsBuilder = ImmutableList.builder(); - if (iterator.hasNext()) { - Snippet firstSnippet = iterator.next(); - stringBuilder.append(firstSnippet.format()); - typesBuilder.addAll(firstSnippet.types()); - argsBuilder.addAll(firstSnippet.args()); - } - while (iterator.hasNext()) { - Snippet nextSnippet = iterator.next(); - stringBuilder.append(", ").append(nextSnippet.format()); - typesBuilder.addAll(nextSnippet.types()); - argsBuilder.addAll(nextSnippet.args()); - } - return new Snippet(stringBuilder.toString(), typesBuilder.build(), argsBuilder.build()); + return join(", ", parameterSnippets); } /** - * A snippet that concatenates its arguments. + * A snippet that concatenates its arguments with each snippet separated by a new line. */ public static Snippet concat(Iterable<Snippet> snippets) { - return join(Joiner.on(""), snippets); + return join("\n", snippets); } /** * A snippet that joins its arguments with {@code joiner}. */ - public static Snippet join(Joiner joiner, Iterable<Snippet> snippets) { - FluentIterable<Snippet> fluentSnippets = FluentIterable.from(snippets); - return new Snippet( - joiner - .appendTo( - new StringBuilder(), - fluentSnippets.transform( - new Function<Snippet, String>() { - @Override - public String apply(Snippet snippet) { - return snippet.format; - } - })) - .toString(), - fluentSnippets - .transformAndConcat( - new Function<Snippet, ImmutableSet<TypeName>>() { - @Override - public ImmutableSet<TypeName> apply(Snippet snippet) { - return snippet.types; - } - }) - .toSet(), - fluentSnippets - .transformAndConcat( - new Function<Snippet, ImmutableList<Object>>() { - @Override - public ImmutableList<Object> apply(Snippet snippet) { - return snippet.args; - } - }) - .toList()); + public static Snippet join(String joinToken, Iterable<Snippet> snippets) { + return new CompoundSnippet(joinToken, ImmutableList.copyOf(snippets)); } } diff --git a/compiler/src/test/java/dagger/internal/codegen/ComponentBuilderTest.java b/compiler/src/test/java/dagger/internal/codegen/ComponentBuilderTest.java index 20bafff65..cf0e69d33 100644 --- a/compiler/src/test/java/dagger/internal/codegen/ComponentBuilderTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/ComponentBuilderTest.java @@ -81,6 +81,7 @@ public class ComponentBuilderTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " }", "", @@ -155,6 +156,7 @@ public class ComponentBuilderTest { " return builder().create();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.stringProvider = TestModule_StringFactory.create(builder.testModule);", " }", @@ -257,6 +259,7 @@ public class ComponentBuilderTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.stringProvider = TestModule1_StringFactory.create(builder.testModule1);", " this.integerProvider = TestModule2_IntegerFactory.create(builder.testModule2);", diff --git a/compiler/src/test/java/dagger/internal/codegen/ComponentProcessorTest.java b/compiler/src/test/java/dagger/internal/codegen/ComponentProcessorTest.java index 94e48e2c8..28e3b4570 100644 --- a/compiler/src/test/java/dagger/internal/codegen/ComponentProcessorTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/ComponentProcessorTest.java @@ -232,6 +232,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " }", "", @@ -318,6 +319,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.someInjectableTypeProvider =", " ScopedProvider.create(SomeInjectableType_Factory.create());", @@ -400,6 +402,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.bMembersInjector =", " OuterType$B_MembersInjector.create(OuterType$A_Factory.create());", @@ -499,6 +502,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.bProvider = TestModule_BFactory.create(builder.testModule,", " C_Factory.create());", @@ -778,6 +782,160 @@ public class ComponentProcessorTest { .compilesWithoutError(); } + @Test + public void subcomponentOmitsInheritedBindings() { + JavaFileObject parent = + JavaFileObjects.forSourceLines( + "test.Parent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = ParentModule.class)", + "interface Parent {", + " Child child();", + "}"); + JavaFileObject parentModule = + JavaFileObjects.forSourceLines( + "test.ParentModule", + "package test;", + "", + "import dagger.mapkeys.StringKey;", + "import dagger.Module;", + "import dagger.Provides;", + "", + "import static dagger.Provides.Type.SET;", + "import static dagger.Provides.Type.MAP;", + "", + "@Module", + "class ParentModule {", + " @Provides(type = SET) static Object parentObject() {", + " return \"parent object\";", + " }", + "", + " @Provides(type = MAP) @StringKey(\"parent key\") Object parentKeyObject() {", + " return \"parent value\";", + " }", + "}"); + JavaFileObject child = + JavaFileObjects.forSourceLines( + "test.Child", + "package test;", + "", + "import dagger.Subcomponent;", + "import java.util.Map;", + "import java.util.Set;", + "", + "@Subcomponent", + "interface Child {", + " Set<Object> objectSet();", + " Map<String, Object> objectMap();", + "}"); + JavaFileObject expected = + JavaFileObjects.forSourceLines( + "test.DaggerParent", + "package test;", + "", + "import dagger.internal.MapFactory;", + "import dagger.internal.MapProviderFactory;", + "import dagger.internal.SetFactory;", + "import java.util.Map;", + "import java.util.Set;", + "import javax.annotation.Generated;", + "import javax.inject.Provider;", + "", + "@Generated(\"dagger.internal.codegen.ComponentProcessor\")", + "public final class DaggerParent implements Parent {", + " private Provider<Set<Object>> setOfObjectContribution1Provider;", + " private Provider<Set<Object>> setOfObjectProvider;", + " private Provider<Object> mapOfStringAndProviderOfObjectContribution1;", + " private Provider<Map<String, Provider<Object>>>", + " mapOfStringAndProviderOfObjectProvider;", + "", + " private DaggerParent(Builder builder) {", + " assert builder != null;", + " initialize(builder);", + " }", + "", + " public static Builder builder() {", + " return new Builder();", + " }", + "", + " public static Parent create() {", + " return builder().build();", + " }", + "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize(final Builder builder) {", + " this.setOfObjectContribution1Provider =", + " ParentModule_ParentObjectFactory.create();", + " this.setOfObjectProvider = SetFactory.create(setOfObjectContribution1Provider);", + " this.mapOfStringAndProviderOfObjectContribution1 =", + " ParentModule_ParentKeyObjectFactory.create(builder.parentModule);", + " this.mapOfStringAndProviderOfObjectProvider =", + " MapProviderFactory.<String, Object>builder(1)", + " .put(\"parent key\", mapOfStringAndProviderOfObjectContribution1)", + " .build();", + " }", + "", + " @Override", + " public Child child() {", + " return new ChildImpl();", + " }", + "", + " public static final class Builder {", + " private ParentModule parentModule;", + "", + " private Builder() {}", + "", + " public Parent build() {", + " if (parentModule == null) {", + " this.parentModule = new ParentModule();", + " }", + " return new DaggerParent(this);", + " }", + "", + " public Builder parentModule(ParentModule parentModule) {", + " if (parentModule == null) {", + " throw new NullPointerException();", + " }", + " this.parentModule = parentModule;", + " return this;", + " }", + " }", + "", + " private final class ChildImpl implements Child {", + " private Provider<Map<String, Object>> mapOfStringAndObjectProvider;", + "", + " private ChildImpl() {", + " initialize();", + " }", + "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.mapOfStringAndObjectProvider = MapFactory.create(", + " DaggerParent.this.mapOfStringAndProviderOfObjectProvider);", + " }", + "", + " @Override", + " public Set<Object> objectSet() {", + " return DaggerParent.this.setOfObjectProvider.get();", + " }", + "", + " @Override", + " public Map<String, Object> objectMap() {", + " return mapOfStringAndObjectProvider.get();", + " }", + " }", + "}"); + assertAbout(javaSources()) + .that(ImmutableList.of(parent, parentModule, child)) + .processedWith(new ComponentProcessor()) + .compilesWithoutError() + .and() + .generatesSources(expected); + } + @Test public void testDefaultPackage() { JavaFileObject aClass = JavaFileObjects.forSourceLines("AClass", "class AClass {}"); JavaFileObject bClass = JavaFileObjects.forSourceLines("BClass", @@ -875,6 +1033,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.setOfStringContribution1Provider =", " EmptySetModule_EmptySetFactory.create(builder.emptySetModule);", @@ -985,6 +1144,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.someInjectedTypeMembersInjector =", " SomeInjectedType_MembersInjector.create(SomeInjectableType_Factory.create());", @@ -1064,6 +1224,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.simpleComponentProvider = InstanceFactory.<SimpleComponent>create(this);", " this.someInjectableTypeProvider =", @@ -1143,6 +1304,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.someInjectedTypeMembersInjector =", " SomeInjectedType_MembersInjector.create(SomeInjectableType_Factory.create());", @@ -1220,6 +1382,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.someInjectableTypeProvider =", " SomeInjectableType_Factory.create((MembersInjector) MembersInjectors.noOp());", @@ -1309,6 +1472,7 @@ public class ComponentProcessorTest { " return new Builder();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.aProvider = new Factory<A>() {", " private final AComponent aComponent = builder.aComponent;", @@ -1429,6 +1593,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.aProvider = test.TestModule_AFactory.create(builder.testModule);", " this.aProvider1 = TestModule_AFactory.create(builder.testModule1);", @@ -1558,6 +1723,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.bProvider = B_Factory.create(C_Factory.create());", " this.aProvider = A_Factory.create(bProvider);", @@ -1660,6 +1826,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {}", "", " @Override", @@ -1748,6 +1915,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {}", "", " @Override", @@ -1957,6 +2125,7 @@ public class ComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.dProvider = new D_Factory(B_Factory.INSTANCE);", " }", diff --git a/compiler/src/test/java/dagger/internal/codegen/DependencyRequestMapperTest.java b/compiler/src/test/java/dagger/internal/codegen/DependencyRequestMapperTest.java index 7766f244a..b47a43c1c 100644 --- a/compiler/src/test/java/dagger/internal/codegen/DependencyRequestMapperTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/DependencyRequestMapperTest.java @@ -57,7 +57,7 @@ public class DependencyRequestMapperTest { this.types = compilationRule.getTypes(); this.elements = compilationRule.getElements(); this.keyFactory = new Key.Factory(types, elements); - this.dependencyRequestFactory = new DependencyRequest.Factory(keyFactory); + this.dependencyRequestFactory = new DependencyRequest.Factory(elements, keyFactory); } private List<? extends VariableElement> sampleProviderParameters() { diff --git a/compiler/src/test/java/dagger/internal/codegen/GraphValidationTest.java b/compiler/src/test/java/dagger/internal/codegen/GraphValidationTest.java index 36303f1e9..6001ea713 100644 --- a/compiler/src/test/java/dagger/internal/codegen/GraphValidationTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/GraphValidationTest.java @@ -28,8 +28,7 @@ import org.junit.runners.JUnit4; import static com.google.common.truth.Truth.assertAbout; import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; -import static dagger.internal.codegen.ErrorMessages.NULLABLE_TO_NON_NULLABLE; -import static java.util.Arrays.asList; +import static dagger.internal.codegen.ErrorMessages.nullableToNonNullable; @RunWith(JUnit4.class) public class GraphValidationTest { @@ -69,25 +68,38 @@ public class GraphValidationTest { } @Test public void componentProvisionWithNoDependencyChain() { - JavaFileObject component = JavaFileObjects.forSourceLines("test.TestClass", - "package test;", - "", - "import dagger.Component;", - "", - "final class TestClass {", - " interface A {}", - "", - " @Component()", - " interface AComponent {", - " A getA();", - " }", - "}"); - String expectedError = - "test.TestClass.A cannot be provided without an @Provides-annotated method."; - assertAbout(javaSource()).that(component) + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "import dagger.Component;", + "import javax.inject.Qualifier;", + "", + "final class TestClass {", + " @Qualifier @interface Q {}", + " interface A {}", + "", + " @Component()", + " interface AComponent {", + " A getA();", + " @Q A qualifiedA();", + " }", + "}"); + assertAbout(javaSource()) + .that(component) .processedWith(new ComponentProcessor()) .failsToCompile() - .withErrorContaining(expectedError).in(component).onLine(10); + .withErrorContaining( + "test.TestClass.A cannot be provided without an @Provides-annotated method.") + .in(component) + .onLine(12) + .and() + .withErrorContaining( + "@test.TestClass.Q test.TestClass.A " + + "cannot be provided without an @Provides-annotated method.") + .in(component) + .onLine(13); } @Test public void constructorInjectionWithoutAnnotation() { @@ -923,8 +935,10 @@ public class GraphValidationTest { assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component)) .processedWith(new ComponentProcessor()) .failsToCompile() - .withErrorContaining(String.format(NULLABLE_TO_NON_NULLABLE, "java.lang.String", - "@test.Nullable @Provides String test.TestModule.provideString()")); + .withErrorContaining( + nullableToNonNullable( + "java.lang.String", + "@test.Nullable @Provides String test.TestModule.provideString()")); // but if we disable the validation, then it compiles fine. assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component)) @@ -965,8 +979,10 @@ public class GraphValidationTest { assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component)) .processedWith(new ComponentProcessor()) .failsToCompile() - .withErrorContaining(String.format(NULLABLE_TO_NON_NULLABLE, "java.lang.String", - "@test.Nullable @Provides String test.TestModule.provideString()")); + .withErrorContaining( + nullableToNonNullable( + "java.lang.String", + "@test.Nullable @Provides String test.TestModule.provideString()")); // but if we disable the validation, then it compiles fine. assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component)) @@ -1007,8 +1023,10 @@ public class GraphValidationTest { assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component)) .processedWith(new ComponentProcessor()) .failsToCompile() - .withErrorContaining(String.format(NULLABLE_TO_NON_NULLABLE, "java.lang.String", - "@test.Nullable @Provides String test.TestModule.provideString()")); + .withErrorContaining( + nullableToNonNullable( + "java.lang.String", + "@test.Nullable @Provides String test.TestModule.provideString()")); // but if we disable the validation, then it compiles fine. assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component)) @@ -1040,8 +1058,10 @@ public class GraphValidationTest { assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, module, component)) .processedWith(new ComponentProcessor()) .failsToCompile() - .withErrorContaining(String.format(NULLABLE_TO_NON_NULLABLE, "java.lang.String", - "@test.Nullable @Provides String test.TestModule.provideString()")); + .withErrorContaining( + nullableToNonNullable( + "java.lang.String", + "@test.Nullable @Provides String test.TestModule.provideString()")); // but if we disable the validation, then it compiles fine. assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, module, component)) diff --git a/compiler/src/test/java/dagger/internal/codegen/PackageProxyTest.java b/compiler/src/test/java/dagger/internal/codegen/InaccessibleTypeTest.java index 8df80d19b..635592214 100644 --- a/compiler/src/test/java/dagger/internal/codegen/PackageProxyTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/InaccessibleTypeTest.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package dagger.internal.codegen; import com.google.common.collect.ImmutableList; @@ -7,12 +22,12 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import static com.google.common.truth.Truth.assert_; +import static com.google.common.truth.Truth.assertAbout; import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; @RunWith(JUnit4.class) -public class PackageProxyTest { - @Test public void basicPackageProxy() { +public class InaccessibleTypeTest { + @Test public void basicInjectedType() { JavaFileObject noDepClassFile = JavaFileObjects.forSourceLines("foreign.NoDepClass", "package foreign;", "", @@ -62,7 +77,6 @@ public class PackageProxyTest { "test.DaggerTestComponent", "package test;", "", - "import foreign.DaggerTestComponent_PackageProxy;", "import foreign.NoDepClass_Factory;", "import foreign.NonPublicClass1_Factory;", "import foreign.NonPublicClass2_Factory;", @@ -73,8 +87,10 @@ public class PackageProxyTest { "", "@Generated(\"dagger.internal.codegen.ComponentProcessor\")", "public final class DaggerTestComponent implements TestComponent {", - " private final DaggerTestComponent_PackageProxy foreign_Proxy =", - " new DaggerTestComponent_PackageProxy();", + " @SuppressWarnings(\"rawtypes\")", + " private Provider nonPublicClass1Provider;", + " @SuppressWarnings(\"rawtypes\")", + " private Provider nonPublicClass2Provider;", " private Provider<PublicClass> publicClassProvider;", "", " private DaggerTestComponent(Builder builder) {", @@ -90,14 +106,15 @@ public class PackageProxyTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", - " this.foreign_Proxy.nonPublicClass1Provider =", + " this.nonPublicClass1Provider =", " NonPublicClass1_Factory.create(NoDepClass_Factory.create());", - " this.foreign_Proxy.nonPublicClass2Provider =", + " this.nonPublicClass2Provider =", " NonPublicClass2_Factory.create(NoDepClass_Factory.create());", " this.publicClassProvider = PublicClass_Factory.create(", - " foreign_Proxy.nonPublicClass1Provider,", - " foreign_Proxy.nonPublicClass2Provider,", + " nonPublicClass1Provider,", + " nonPublicClass2Provider,", " NoDepClass_Factory.create());", " }", "", @@ -115,19 +132,20 @@ public class PackageProxyTest { " }", " }", "}"); - assert_().about(javaSources()) + assertAbout(javaSources()) .that(ImmutableList.of( noDepClassFile, publicClassFile, nonPublicClass1File, nonPublicClass2File, componentFile)) + .withCompilerOptions("-Xlint:rawtypes", "-Xlint:unchecked", "-Werror") .processedWith(new ComponentProcessor()) .compilesWithoutError() .and().generatesSources(generatedComponent); } - @Test public void memberInjectionPackageProxy() { + @Test public void memberInjectedType() { JavaFileObject noDepClassFile = JavaFileObjects.forSourceLines("test.NoDepClass", "package test;", "", @@ -209,6 +227,7 @@ public class PackageProxyTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.aMembersInjector = A_MembersInjector.create(NoDepClass_Factory.create());", " }", @@ -227,7 +246,7 @@ public class PackageProxyTest { " }", " }", "}"); - assert_().about(javaSources()) + assertAbout(javaSources()) .that(ImmutableList.of( noDepClassFile, aClassFile, @@ -235,6 +254,7 @@ public class PackageProxyTest { cClassFile, dClassFile, componentFile)) + .withCompilerOptions("-Xlint:rawtypes", "-Xlint:unchecked", "-Werror") .processedWith(new ComponentProcessor()) .compilesWithoutError() .and().generatesSources(generatedComponent); diff --git a/compiler/src/test/java/dagger/internal/codegen/MapBindingComponentProcessorTest.java b/compiler/src/test/java/dagger/internal/codegen/MapBindingComponentProcessorTest.java index 5f488c814..9e1b6dccd 100644 --- a/compiler/src/test/java/dagger/internal/codegen/MapBindingComponentProcessorTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/MapBindingComponentProcessorTest.java @@ -137,6 +137,7 @@ public class MapBindingComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.mapOfPathEnumAndProviderOfHandlerContribution1 =", " MapModuleOne_ProvideAdminHandlerFactory.create(builder.mapModuleOne);", @@ -296,6 +297,7 @@ public class MapBindingComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.mapOfStringAndProviderOfHandlerContribution1 =", " MapModuleOne_ProvideAdminHandlerFactory.create(builder.mapModuleOne);", @@ -462,6 +464,7 @@ public class MapBindingComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.mapOfWrappedClassKeyAndProviderOfHandlerContribution1 =", " MapModuleOne_ProvideAdminHandlerFactory.create(builder.mapModuleOne);", @@ -634,6 +637,7 @@ public class MapBindingComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.mapOfPathEnumAndProviderOfHandlerContribution1 =", " MapModuleOne_ProvideAdminHandlerFactory.create(builder.mapModuleOne);", @@ -755,6 +759,7 @@ public class MapBindingComponentProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.provideAMapProvider = MapModule_ProvideAMapFactory.create(builder.mapModule);", " }", diff --git a/compiler/src/test/java/dagger/internal/codegen/MapKeyProcessorTest.java b/compiler/src/test/java/dagger/internal/codegen/MapKeyProcessorTest.java index bc8a2660e..191ee6c12 100644 --- a/compiler/src/test/java/dagger/internal/codegen/MapKeyProcessorTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/MapKeyProcessorTest.java @@ -226,6 +226,7 @@ public class MapKeyProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.mapOfPathKeyAndProviderOfHandlerContribution1 =", " MapModuleOne_ProvideAdminHandlerFactory.create(builder.mapModuleOne);", @@ -401,6 +402,7 @@ public class MapKeyProcessorTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.mapOfPathKeyAndProviderOfHandlerContribution1 =", " MapModuleOne_ProvideAdminHandlerFactory.create(builder.mapModuleOne);", diff --git a/compiler/src/test/java/dagger/internal/codegen/MembersInjectionTest.java b/compiler/src/test/java/dagger/internal/codegen/MembersInjectionTest.java index 7925bd222..52be72ae9 100644 --- a/compiler/src/test/java/dagger/internal/codegen/MembersInjectionTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/MembersInjectionTest.java @@ -33,6 +33,7 @@ import org.junit.runners.JUnit4; import static com.google.common.truth.Truth.assertAbout; import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; +import static javax.tools.StandardLocation.CLASS_OUTPUT; @RunWith(JUnit4.class) public class MembersInjectionTest { @@ -87,6 +88,7 @@ public class MembersInjectionTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.childProvider =", " Child_Factory.create((MembersInjector) MembersInjectors.noOp());", @@ -176,6 +178,7 @@ public class MembersInjectionTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.childMembersInjector = Child_MembersInjector.create(Dep_Factory.create());", " this.childProvider = Child_Factory.create(childMembersInjector);", @@ -872,4 +875,47 @@ public class MembersInjectionTest { .and() .generatesSources(bMembersInjector); } + + @Test + public void lowerCaseNamedMembersInjector_forLowerCaseType() { + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.foo", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class foo {", + " @Inject String string;", + "}"); + JavaFileObject fooModule = + JavaFileObjects.forSourceLines( + "test.fooModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "", + "@Module", + "class fooModule {", + " @Provides String string() { return \"foo\"; }", + "}"); + JavaFileObject fooComponent = + JavaFileObjects.forSourceLines( + "test.fooComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = fooModule.class)", + "interface fooComponent {", + " void inject(foo target);", + "}"); + + assertAbout(javaSources()) + .that(ImmutableList.of(foo, fooModule, fooComponent)) + .processedWith(new ComponentProcessor()) + .compilesWithoutError() + .and().generatesFileNamed(CLASS_OUTPUT, "test", "foo_MembersInjector.class"); + } } diff --git a/compiler/src/test/java/dagger/internal/codegen/MissingBindingSuggestionsTest.java b/compiler/src/test/java/dagger/internal/codegen/MissingBindingSuggestionsTest.java index 0ae01b40c..03ec35d9b 100644 --- a/compiler/src/test/java/dagger/internal/codegen/MissingBindingSuggestionsTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/MissingBindingSuggestionsTest.java @@ -15,17 +15,14 @@ */ package dagger.internal.codegen; -import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.testing.compile.JavaFileObjects; -import java.util.Arrays; import javax.tools.JavaFileObject; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import static com.google.common.truth.Truth.assertAbout; -import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; @RunWith(JUnit4.class) diff --git a/compiler/src/test/java/dagger/internal/codegen/ProducerModuleFactoryGeneratorTest.java b/compiler/src/test/java/dagger/internal/codegen/ProducerModuleFactoryGeneratorTest.java index 0041f5d89..f9c287859 100644 --- a/compiler/src/test/java/dagger/internal/codegen/ProducerModuleFactoryGeneratorTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/ProducerModuleFactoryGeneratorTest.java @@ -471,53 +471,52 @@ public class ProducerModuleFactoryGeneratorTest { "TestModule_ProduceStringFactory", "package test;", "", + "import com.google.common.util.concurrent.AsyncFunction;", "import com.google.common.util.concurrent.Futures;", "import com.google.common.util.concurrent.ListenableFuture;", "import dagger.producers.internal.AbstractProducer;", - "import dagger.producers.internal.Producers;", + "import dagger.producers.monitoring.ProducerMonitor;", "import dagger.producers.monitoring.ProducerToken;", "import dagger.producers.monitoring.ProductionComponentMonitor;", - "import java.util.concurrent.Callable;", "import java.util.concurrent.Executor;", "import javax.annotation.Generated;", - "import javax.annotation.Nullable;", + "import javax.inject.Provider;", "", "@Generated(\"dagger.internal.codegen.ComponentProcessor\")", "public final class TestModule_ProduceStringFactory extends AbstractProducer<String> {", " private final TestModule module;", " private final Executor executor;", + " private final Provider<ProductionComponentMonitor> monitorProvider;", "", " public TestModule_ProduceStringFactory(", - " @Nullable ProductionComponentMonitor componentMonitor,", " TestModule module,", - " Executor executor) {", + " Executor executor,", + " Provider<ProductionComponentMonitor> monitorProvider) {", " super(", - " Producers.producerMonitorFor(", - " componentMonitor,", - " ProducerToken.create(TestModule_ProduceStringFactory.class)));", + " monitorProvider,", + " ProducerToken.create(TestModule_ProduceStringFactory.class));", " assert module != null;", " this.module = module;", " assert executor != null;", " this.executor = executor;", + " assert monitorProvider != null;", + " this.monitorProvider = monitorProvider;", " }", "", - " @Override protected ListenableFuture<String> compute() {", - " ListenableFuture<ListenableFuture<String>> future = Producers.submitToExecutor(", - " new Callable<ListenableFuture<String>>() {", - " @Override public ListenableFuture<String> call() {", - " if (monitor != null) {", - " monitor.methodStarting();", - " }", + " @Override protected ListenableFuture<String> compute(", + " final ProducerMonitor monitor) {", + " return Futures.transform(", + " Futures.<Void>immediateFuture(null),", + " new AsyncFunction<Void, String>() {", + " @Override public ListenableFuture<String> apply(Void ignoredVoidArg) {", + " monitor.methodStarting();", " try {", " return module.produceString();", " } finally {", - " if (monitor != null) {", - " monitor.methodFinished();", - " }", + " monitor.methodFinished();", " }", " }", " }, executor);", - " return Futures.dereference(future);", " }", "}"); assertAbout(javaSource()) diff --git a/compiler/src/test/java/dagger/internal/codegen/ProductionComponentProcessorTest.java b/compiler/src/test/java/dagger/internal/codegen/ProductionComponentProcessorTest.java index 92bbb752c..a8e39b28a 100644 --- a/compiler/src/test/java/dagger/internal/codegen/ProductionComponentProcessorTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/ProductionComponentProcessorTest.java @@ -123,98 +123,136 @@ public class ProductionComponentProcessorTest { " ListenableFuture<A> a();", " }", "}"); - JavaFileObject generatedComponent = JavaFileObjects.forSourceLines( - "test.DaggerTestClass_SimpleComponent", - "package test;", - "", - "import com.google.common.util.concurrent.ListenableFuture;", - "import dagger.producers.Producer;", - "import dagger.producers.internal.Producers;", - "import java.util.concurrent.Executor;", - "import javax.annotation.Generated;", - "import javax.inject.Provider;", - "import test.TestClass.A;", - "import test.TestClass.AModule;", - "import test.TestClass.B;", - "import test.TestClass.BModule;", - "import test.TestClass.SimpleComponent;", - "", - "@Generated(\"dagger.internal.codegen.ComponentProcessor\")", - "public final class DaggerTestClass_SimpleComponent implements SimpleComponent {", - " private Provider<B> bProvider;", - " private Producer<A> aProducer;", - "", - " private DaggerTestClass_SimpleComponent(Builder builder) {", - " assert builder != null;", - " initialize(builder);", - " }", - "", - " public static Builder builder() {", - " return new Builder();", - " }", - "", - " private void initialize(final Builder builder) {", - " this.bProvider = TestClass$BModule_BFactory.create(", - " builder.bModule, TestClass$C_Factory.create());", - " this.aProducer = new TestClass$AModule_AFactory(", - " null,", - " builder.aModule,", - " builder.executor,", - " Producers.producerFromProvider(bProvider));", - " }", - "", - " @Override", - " public ListenableFuture<A> a() {", - " return aProducer.get();", - " }", - "", - " public static final class Builder {", - " private BModule bModule;", - " private AModule aModule;", - " private Executor executor;", - "", - " private Builder() {", - " }", - "", - " public SimpleComponent build() {", - " if (bModule == null) {", - " this.bModule = new BModule();", - " }", - " if (aModule == null) {", - " this.aModule = new AModule();", - " }", - " if (executor == null) {", - " throw new IllegalStateException(Executor.class.getCanonicalName()", - " + \" must be set\");", - " }", - " return new DaggerTestClass_SimpleComponent(this);", - " }", - "", - " public Builder aModule(AModule aModule) {", - " if (aModule == null) {", - " throw new NullPointerException();", - " }", - " this.aModule = aModule;", - " return this;", - " }", - "", - " public Builder bModule(BModule bModule) {", - " if (bModule == null) {", - " throw new NullPointerException();", - " }", - " this.bModule = bModule;", - " return this;", - " }", - "", - " public Builder executor(Executor executor) {", - " if (executor == null) {", - " throw new NullPointerException();", - " }", - " this.executor = executor;", - " return this;", - " }", - " }", - "}"); + JavaFileObject generatedComponent = + JavaFileObjects.forSourceLines( + "test.DaggerTestClass_SimpleComponent", + "package test;", + "", + "import com.google.common.util.concurrent.ListenableFuture;", + "import dagger.internal.InstanceFactory;", + "import dagger.internal.SetFactory;", + "import dagger.producers.Producer;", + "import dagger.producers.internal.Producers;", + "import dagger.producers.monitoring.ProductionComponentMonitor;", + "import dagger.producers.monitoring.ProductionComponentMonitor.Factory;", + "import java.util.Set;", + "import java.util.concurrent.Executor;", + "import javax.annotation.Generated;", + "import javax.inject.Provider;", + "import test.TestClass.A;", + "import test.TestClass.AModule;", + "import test.TestClass.B;", + "import test.TestClass.BModule;", + "import test.TestClass.SimpleComponent;", + "", + "@Generated(\"dagger.internal.codegen.ComponentProcessor\")", + "public final class DaggerTestClass_SimpleComponent implements SimpleComponent {", + " private Provider<SimpleComponent> simpleComponentProvider;", + " private Provider<Set<Factory>> setOfFactoryContribution1Provider;", + " private Provider<Set<Factory>> setOfFactoryProvider;", + " private Provider<ProductionComponentMonitor> monitorProvider;", + " private Provider<B> bProvider;", + " private Producer<A> aProducer;", + "", + " private DaggerTestClass_SimpleComponent(Builder builder) {", + " assert builder != null;", + " initialize(builder);", + " }", + "", + " public static Builder builder() {", + " return new Builder();", + " }", + "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize(final Builder builder) {", + " this.simpleComponentProvider = InstanceFactory.<SimpleComponent>create(this);", + " this.setOfFactoryContribution1Provider =", + " TestClass$SimpleComponent_MonitoringModule_DefaultSetOfFactoriesFactory", + " .create();", + " this.setOfFactoryProvider = SetFactory.create(setOfFactoryContribution1Provider);", + " this.monitorProvider =", + " TestClass$SimpleComponent_MonitoringModule_MonitorFactory.create(", + " builder.testClass$SimpleComponent_MonitoringModule,", + " simpleComponentProvider,", + " setOfFactoryProvider);", + " this.bProvider = TestClass$BModule_BFactory.create(", + " builder.bModule, TestClass$C_Factory.create());", + " this.aProducer = new TestClass$AModule_AFactory(", + " builder.aModule,", + " builder.executor,", + " monitorProvider,", + " Producers.producerFromProvider(bProvider));", + " }", + "", + " @Override", + " public ListenableFuture<A> a() {", + " return aProducer.get();", + " }", + "", + " public static final class Builder {", + " private TestClass$SimpleComponent_MonitoringModule", + " testClass$SimpleComponent_MonitoringModule;", + " private BModule bModule;", + " private AModule aModule;", + " private Executor executor;", + "", + " private Builder() {", + " }", + "", + " public SimpleComponent build() {", + " if (testClass$SimpleComponent_MonitoringModule == null) {", + " this.testClass$SimpleComponent_MonitoringModule =", + " new TestClass$SimpleComponent_MonitoringModule();", + " }", + " if (bModule == null) {", + " this.bModule = new BModule();", + " }", + " if (aModule == null) {", + " this.aModule = new AModule();", + " }", + " if (executor == null) {", + " throw new IllegalStateException(Executor.class.getCanonicalName()", + " + \" must be set\");", + " }", + " return new DaggerTestClass_SimpleComponent(this);", + " }", + "", + " public Builder aModule(AModule aModule) {", + " if (aModule == null) {", + " throw new NullPointerException();", + " }", + " this.aModule = aModule;", + " return this;", + " }", + "", + " public Builder bModule(BModule bModule) {", + " if (bModule == null) {", + " throw new NullPointerException();", + " }", + " this.bModule = bModule;", + " return this;", + " }", + "", + " public Builder testClass$SimpleComponent_MonitoringModule(", + " TestClass$SimpleComponent_MonitoringModule", + " testClass$SimpleComponent_MonitoringModule) {", + " if (testClass$SimpleComponent_MonitoringModule == null) {", + " throw new NullPointerException();", + " }", + " this.testClass$SimpleComponent_MonitoringModule =", + " testClass$SimpleComponent_MonitoringModule;", + " return this;", + " }", + "", + " public Builder executor(Executor executor) {", + " if (executor == null) {", + " throw new NullPointerException();", + " }", + " this.executor = executor;", + " return this;", + " }", + " }", + "}"); assertAbout(javaSource()).that(component) .processedWith(new ComponentProcessor()) .compilesWithoutError() diff --git a/compiler/src/test/java/dagger/internal/codegen/ProductionGraphValidationTest.java b/compiler/src/test/java/dagger/internal/codegen/ProductionGraphValidationTest.java index 9f577b722..6fc871684 100644 --- a/compiler/src/test/java/dagger/internal/codegen/ProductionGraphValidationTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/ProductionGraphValidationTest.java @@ -161,4 +161,110 @@ public class ProductionGraphValidationTest { .failsToCompile() .withErrorContaining(expectedError).in(component).onLine(20); } + + @Test + public void monitoringDependsOnUnboundType() { + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "import com.google.common.util.concurrent.ListenableFuture;", + "import dagger.Module;", + "import dagger.Provides;", + "import dagger.producers.ProducerModule;", + "import dagger.producers.Produces;", + "import dagger.producers.ProductionComponent;", + "import dagger.producers.monitoring.ProductionComponentMonitor;", + "", + "import static dagger.Provides.Type.SET;", + "", + "final class TestClass {", + " interface A {}", + "", + " @Module", + " final class MonitoringModule {", + " @Provides(type = SET)", + " ProductionComponentMonitor.Factory monitorFactory(A unbound) {", + " return null;", + " }", + " }", + "", + " @ProducerModule", + " final class StringModule {", + " @Produces ListenableFuture<String> str() {", + " return null;", + " }", + " }", + "", + " @ProductionComponent(modules = {MonitoringModule.class, StringModule.class})", + " interface StringComponent {", + " ListenableFuture<String> getString();", + " }", + "}"); + String expectedError = + "test.TestClass.A cannot be provided without an @Provides-annotated method."; + assertAbout(javaSource()) + .that(component) + .processedWith(new ComponentProcessor()) + .failsToCompile() + .withErrorContaining(expectedError) + .in(component) + .onLine(33); + } + + @Test + public void monitoringDependsOnProduction() { + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "import com.google.common.util.concurrent.ListenableFuture;", + "import dagger.Module;", + "import dagger.Provides;", + "import dagger.producers.ProducerModule;", + "import dagger.producers.Produces;", + "import dagger.producers.ProductionComponent;", + "import dagger.producers.monitoring.ProductionComponentMonitor;", + "", + "import static dagger.Provides.Type.SET;", + "", + "final class TestClass {", + " interface A {}", + "", + " @Module", + " final class MonitoringModule {", + " @Provides(type = SET) ProductionComponentMonitor.Factory monitorFactory(A a) {", + " return null;", + " }", + " }", + "", + " @ProducerModule", + " final class StringModule {", + " @Produces A a() {", + " return null;", + " }", + "", + " @Produces ListenableFuture<String> str() {", + " return null;", + " }", + " }", + "", + " @ProductionComponent(modules = {MonitoringModule.class, StringModule.class})", + " interface StringComponent {", + " ListenableFuture<String> getString();", + " }", + "}"); + String expectedError = + "java.util.Set<dagger.producers.monitoring.ProductionComponentMonitor.Factory> is a" + + " provision, which cannot depend on a production."; + assertAbout(javaSource()) + .that(component) + .processedWith(new ComponentProcessor()) + .failsToCompile() + .withErrorContaining(expectedError) + .in(component) + .onLine(36); + } } diff --git a/compiler/src/test/java/dagger/internal/codegen/SubcomponentValidationTest.java b/compiler/src/test/java/dagger/internal/codegen/SubcomponentValidationTest.java index 05ef5f37a..d47c32839 100644 --- a/compiler/src/test/java/dagger/internal/codegen/SubcomponentValidationTest.java +++ b/compiler/src/test/java/dagger/internal/codegen/SubcomponentValidationTest.java @@ -43,15 +43,26 @@ public final class SubcomponentValidationTest { "import dagger.Subcomponent;", "", "@Subcomponent(modules = ModuleWithParameters.class)", - "interface ChildComponent {}"); + "interface ChildComponent {", + " Object object();", + "}"); JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.ModuleWithParameters", "package test;", "", "import dagger.Module;", + "import dagger.Provides;", "", "@Module", "final class ModuleWithParameters {", - " ModuleWithParameters(Object whatever) {}", + " private final Object object;", + "", + " ModuleWithParameters(Object object) {", + " this.object = object;", + " }", + "", + " @Provides Object object() {", + " return object;", + " }", "}"); assertAbout(javaSources()).that(ImmutableList.of(componentFile, childComponentFile, moduleFile)) .processedWith(new ComponentProcessor()) @@ -244,7 +255,7 @@ public final class SubcomponentValidationTest { .failsToCompile() .withErrorContaining("@Singleton"); } - + @Test public void delegateFactoryNotCreatedForSubcomponentWhenProviderExistsInParent() { JavaFileObject parentComponentFile = @@ -356,6 +367,7 @@ public final class SubcomponentValidationTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) { ", " this.dep1MembersInjector = Dep1_MembersInjector.create();", " this.dep1Provider = Dep1_Factory.create(dep1MembersInjector);", @@ -393,14 +405,13 @@ public final class SubcomponentValidationTest { " private Provider<NeedsDep1> needsDep1Provider;", " private Provider<A> aProvider;", " private Provider<Object> provideObjectProvider;", - " private MembersInjector<Dep1> dep1MembersInjector;", - " private MembersInjector<Dep2> dep2MembersInjector;", " ", " private ChildComponentImpl() { ", " this.childModule = new ChildModule();", " initialize();", " }", - " ", + "", + " @SuppressWarnings(\"unchecked\")", " private void initialize() { ", " this.aMembersInjector = A_MembersInjector.create();", " this.needsDep1Provider = NeedsDep1_Factory.create(", @@ -412,8 +423,6 @@ public final class SubcomponentValidationTest { " DaggerParentComponent.this.dep2Provider);", " this.provideObjectProvider = ChildModule_ProvideObjectFactory.create(", " childModule, aProvider);", - " this.dep1MembersInjector = Dep1_MembersInjector.create();", - " this.dep2MembersInjector = Dep2_MembersInjector.create();", " }", " ", " @Override", diff --git a/compiler/src/test/java/dagger/tests/integration/operation/PrimitiveInjectionTest.java b/compiler/src/test/java/dagger/tests/integration/operation/PrimitiveInjectionTest.java index 40545039c..58fa26307 100644 --- a/compiler/src/test/java/dagger/tests/integration/operation/PrimitiveInjectionTest.java +++ b/compiler/src/test/java/dagger/tests/integration/operation/PrimitiveInjectionTest.java @@ -92,6 +92,7 @@ public final class PrimitiveInjectionTest { " return builder().build();", " }", "", + " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) {", " this.primitiveIntProvider =", " PrimitiveModule_PrimitiveIntFactory.create(builder.primitiveModule);", diff --git a/core/src/main/java/dagger/Subcomponent.java b/core/src/main/java/dagger/Subcomponent.java index 0ef47011f..988f17b49 100644 --- a/core/src/main/java/dagger/Subcomponent.java +++ b/core/src/main/java/dagger/Subcomponent.java @@ -16,9 +16,11 @@ package dagger; import java.lang.annotation.Documented; +import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * A subcomponent that inherits the bindings from a parent {@link Component} or @@ -28,6 +30,7 @@ import static java.lang.annotation.ElementType.TYPE; * @author Gregory Kick * @since 2.0 */ +@Retention(RUNTIME) // Allows runtimes to have specialized behavior interoperating with Dagger. @Target(TYPE) @Documented public @interface Subcomponent { diff --git a/dagger2_annotation_processor.mk b/dagger2_annotation_processor.mk new file mode 100644 index 000000000..f80b83cb2 --- /dev/null +++ b/dagger2_annotation_processor.mk @@ -0,0 +1,19 @@ +# Copyright (C) 2015 The Android Open Source Project +# +# 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. + +DAGGER2_PROCESSOR_LIBRARIES := \ + dagger2-compiler-host + +DAGGER2_PROCESSOR_CLASSES := \ + dagger.internal.codegen.ComponentProcessor diff --git a/examples/android-activity-graphs/pom.xml b/examples/android-activity-graphs/pom.xml index 53de228ff..340c59c54 100644 --- a/examples/android-activity-graphs/pom.xml +++ b/examples/android-activity-graphs/pom.xml @@ -37,6 +37,7 @@ <groupId>com.google.dagger</groupId> <artifactId>dagger-compiler</artifactId> <version>${project.version}</version> + <scope>provided</scope> <optional>true</optional> </dependency> @@ -54,7 +55,7 @@ <build> <plugins> <plugin> - <groupId>com.jayway.maven.plugins.android.generation2</groupId> + <groupId>com.simpligility.maven.plugins</groupId> <artifactId>android-maven-plugin</artifactId> <extensions>true</extensions> </plugin> diff --git a/examples/android-activity-graphs/AndroidManifest.xml b/examples/android-activity-graphs/src/main/AndroidManifest.xml index 234406dfa..234406dfa 100644 --- a/examples/android-activity-graphs/AndroidManifest.xml +++ b/examples/android-activity-graphs/src/main/AndroidManifest.xml diff --git a/examples/android-simple/pom.xml b/examples/android-simple/pom.xml index 332aeb66b..fb934e6d6 100644 --- a/examples/android-simple/pom.xml +++ b/examples/android-simple/pom.xml @@ -37,6 +37,7 @@ <groupId>com.google.dagger</groupId> <artifactId>dagger-compiler</artifactId> <version>${project.version}</version> + <scope>provided</scope> <optional>true</optional> </dependency> @@ -50,7 +51,7 @@ <build> <plugins> <plugin> - <groupId>com.jayway.maven.plugins.android.generation2</groupId> + <groupId>com.simpligility.maven.plugins</groupId> <artifactId>android-maven-plugin</artifactId> <extensions>true</extensions> </plugin> diff --git a/examples/android-simple/AndroidManifest.xml b/examples/android-simple/src/main/AndroidManifest.xml index 53c83bfd3..53c83bfd3 100644 --- a/examples/android-simple/AndroidManifest.xml +++ b/examples/android-simple/src/main/AndroidManifest.xml diff --git a/examples/pom.xml b/examples/pom.xml index 708635f1a..eb4685aa0 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -62,12 +62,13 @@ </configuration> </plugin> <plugin> - <groupId>com.jayway.maven.plugins.android.generation2</groupId> + <groupId>com.simpligility.maven.plugins</groupId> <artifactId>android-maven-plugin</artifactId> - <version>3.8.2</version> + <version>4.3.0</version> <configuration> <sdk> - <platform>16</platform> + <platform>23</platform> + <path>${env.ANDROID_HOME}</path> </sdk> </configuration> </plugin> diff --git a/java_annotation_processors.mk b/java_annotation_processors.mk new file mode 100644 index 000000000..18ef29ba1 --- /dev/null +++ b/java_annotation_processors.mk @@ -0,0 +1,69 @@ +# Copyright (C) 2015 The Android Open Source Project +# +# 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. + +# Manages use of annotation processors. +# +# At the moment both the -processorpath and the -processor +# flags must be specified in order to use annotation processors +# as a code indexing tool that wraps javac doesn't as yet support +# the same behaviour as standard javac with regard to use of +# annotation processors. In particular it: +# - doesn't default -processorpath to be the same as -classpath +# - doesn't scan processorpath to automatically discover processors +# - doesn't support a comma separated list of processor class names +# on a single -processor option so need on option per class name. +# +# Input variables: +# +# PROCESSOR_LIBRARIES := <list of library names> +# Similar to names added to LOCAL_JAVA_LIBRARIES. +# +# PROCESSOR_CLASSES := <list of processor class names> +# +# Upon exit various LOCAL_ variables have been updated and the +# input variables have been cleared. + +# Map the library names to actual JARs. +PROCESSOR_JARS := $(call java-lib-deps, $(PROCESSOR_LIBRARIES), true) + +# Add a javac -processorpath flag. +LOCAL_JAVACFLAGS += -processorpath $(call normalize-path-list,$(PROCESSOR_JARS)) + +# Specify only one processor class per -processor option as +# the indexing tool does not parse the -processor value as a +# comma separated list. +LOCAL_JAVACFLAGS += $(foreach class,$(PROCESSOR_CLASSES),-processor $(class)) + +# Create a source directory into which the code will be generated. +GENERATED_SOURCE_DIR := $(local-generated-sources-dir)/annotation_processor_output/ + +# Tell javac to generate source files in the source directory. +LOCAL_JAVACFLAGS += -s $(GENERATED_SOURCE_DIR) +LOCAL_GENERATED_SOURCES := $(GENERATED_SOURCE_DIR) + +# Add dependency between the jar being built and the processor jars so that +# they are built before this one. +LOCAL_ADDITIONAL_DEPENDENCIES += $(PROCESSOR_JARS) $(GENERATED_SOURCE_DIR) + +$(GENERATED_SOURCE_DIR): + mkdir -p $@ + +LOCAL_JACK_ENABLED := disabled + +# Clean up all the extra variables to make sure that they don't escape to +# another module. +PROCESSOR_LIBRARIES := +PROCESSOR_CLASSES := +PROCESSOR_JARS := +GENERATED_SOURCE_DIR := @@ -44,15 +44,17 @@ <!-- Compilation --> <java.version>1.7</java.version> <javax.inject.version>1</javax.inject.version> + <javax.annotation.version>2.0.1</javax.annotation.version> <javawriter.version>2.5.0</javawriter.version> <auto.common.version>1.0-SNAPSHOT</auto.common.version> - <auto.factory.version>1.0-SNAPSHOT</auto.factory.version> + <auto.factory.version>1.0-beta3</auto.factory.version> <auto.service.version>1.0-rc2</auto.service.version> <auto.value.version>1.0</auto.value.version> <guava.version>18.0</guava.version> + <google.java.format.version>0.1-SNAPSHOT</google.java.format.version> <!-- Test Dependencies --> - <compile-testing.version>0.7</compile-testing.version> + <compile-testing.version>1.0-SNAPSHOT</compile-testing.version> <junit.version>4.11</junit.version> <mockito.version>1.9.5</mockito.version> <truth.version>0.26</truth.version> @@ -89,6 +91,16 @@ <artifactId>javax.inject</artifactId> <version>${javax.inject.version}</version> </dependency> + <dependency> + <groupId>javax.inject</groupId> + <artifactId>javax.inject-tck</artifactId> + <version>${javax.inject.version}</version> + </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <version>${javax.annotation.version}</version> + </dependency> <dependency> <groupId>com.squareup</groupId> <artifactId>javawriter</artifactId> @@ -105,6 +117,11 @@ <version>${guava.version}</version> </dependency> <dependency> + <groupId>com.google.googlejavaformat</groupId> + <artifactId>google-java-format</artifactId> + <version>${google.java.format.version}</version> + </dependency> + <dependency> <groupId>com.google.auto</groupId> <artifactId>auto-common</artifactId> <version>${auto.common.version}</version> diff --git a/producers/pom.xml b/producers/pom.xml index 1ee555b97..edaeca428 100644 --- a/producers/pom.xml +++ b/producers/pom.xml @@ -36,6 +36,11 @@ <version>${project.version}</version> </dependency> <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <optional>true</optional> + </dependency> + <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </dependency> @@ -50,6 +55,11 @@ <scope>test</scope> </dependency> <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava-testlib</artifactId> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> diff --git a/producers/src/main/java/dagger/producers/Produced.java b/producers/src/main/java/dagger/producers/Produced.java index 7edfee314..db5c133ff 100644 --- a/producers/src/main/java/dagger/producers/Produced.java +++ b/producers/src/main/java/dagger/producers/Produced.java @@ -64,7 +64,7 @@ public abstract class Produced<T> { /** Returns a successful {@code Produced}, whose {@link #get} will return the given value. */ public static <T> Produced<T> successful(@Nullable T value) { - return new Successful(value); + return new Successful<T>(value); } /** @@ -72,7 +72,7 @@ public abstract class Produced<T> { * {@code ExecutionException} with the given cause. */ public static <T> Produced<T> failed(Throwable throwable) { - return new Failed(checkNotNull(throwable)); + return new Failed<T>(checkNotNull(throwable)); } private static final class Successful<T> extends Produced<T> { @@ -90,7 +90,7 @@ public abstract class Produced<T> { if (o == this) { return true; } else if (o instanceof Successful) { - Successful that = (Successful) o; + Successful<?> that = (Successful<?>) o; return Objects.equal(this.value, that.value); } else { return false; @@ -117,7 +117,7 @@ public abstract class Produced<T> { if (o == this) { return true; } else if (o instanceof Failed) { - Failed that = (Failed) o; + Failed<?> that = (Failed<?>) o; return this.throwable.equals(that.throwable); } else { return false; diff --git a/producers/src/main/java/dagger/producers/ProductionComponent.java b/producers/src/main/java/dagger/producers/ProductionComponent.java index a6009c5b3..8ccdb4433 100644 --- a/producers/src/main/java/dagger/producers/ProductionComponent.java +++ b/producers/src/main/java/dagger/producers/ProductionComponent.java @@ -80,4 +80,42 @@ public @interface ProductionComponent { * A list of types that are to be used as component dependencies. */ Class<?>[] dependencies() default {}; + + /** + * A builder for a component. Components may have a single nested static abstract class or + * interface annotated with {@code @ProductionComponent.Builder}. If they do, then the component's + * generated builder will match the API in the type. Builders must follow some rules: + * <ul> + * <li> A single abstract method with no arguments must exist, and must return the component. + * (This is typically the {@code build()} method.) + * <li> All other abstract methods must take a single argument and must return void, + * the builder type, or a supertype of the builder. + * <li> There <b>must</b> be an abstract method whose parameter is + * {@link java.util.concurrent.Executor}. + * <li> Each component dependency <b>must</b> have an abstract setter method. + * <li> Each module dependency that Dagger can't instantiate itself (i.e., the module + * doesn't have a visible no-args constructor) <b>must</b> have an abstract setter method. + * Other module dependencies (ones that Dagger can instantiate) are allowed, but not + * required. + * <li> Non-abstract methods are allowed, but ignored as far as validation and builder generation + * are concerned. + * </ul> + * + * For example, this could be a valid {@code ProductionComponent} with a builder: <pre><code> + * {@literal @}ProductionComponent(modules = {BackendModule.class, FrontendModule.class}) + * interface MyComponent { + * {@literal ListenableFuture<MyWidget>} myWidget(); + * + * {@literal @}ProductionComponent.Builder + * interface Builder { + * MyComponent build(); + * Builder executor(Executor executor); + * Builder backendModule(BackendModule bm); + * Builder frontendModule(FrontendModule fm); + * } + * }</code></pre> + */ + @Target(TYPE) + @Documented + @interface Builder {} } diff --git a/producers/src/main/java/dagger/producers/internal/AbstractProducer.java b/producers/src/main/java/dagger/producers/internal/AbstractProducer.java index 7843b2094..f7e8ec0e5 100644 --- a/producers/src/main/java/dagger/producers/internal/AbstractProducer.java +++ b/producers/src/main/java/dagger/producers/internal/AbstractProducer.java @@ -15,12 +15,17 @@ */ package dagger.producers.internal; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import dagger.producers.Producer; import dagger.producers.monitoring.ProducerMonitor; +import dagger.producers.monitoring.ProducerToken; +import dagger.producers.monitoring.ProductionComponentMonitor; +import dagger.producers.monitoring.internal.Monitors; + +import static com.google.common.base.Preconditions.checkNotNull; + import javax.annotation.Nullable; +import javax.inject.Provider; /** * An abstract {@link Producer} implementation that memoizes the result of its compute method. @@ -29,19 +34,22 @@ import javax.annotation.Nullable; * @since 2.0 */ public abstract class AbstractProducer<T> implements Producer<T> { - @Nullable protected final ProducerMonitor monitor; + private final Provider<ProductionComponentMonitor> monitorProvider; + @Nullable private final ProducerToken token; private volatile ListenableFuture<T> instance = null; protected AbstractProducer() { - this(null); + this(Monitors.noOpProductionComponentMonitorProvider(), null); } - protected AbstractProducer(@Nullable ProducerMonitor monitor) { - this.monitor = monitor; + protected AbstractProducer( + Provider<ProductionComponentMonitor> monitorProvider, @Nullable ProducerToken token) { + this.monitorProvider = checkNotNull(monitorProvider); + this.token = token; } /** Computes this producer's future, which is then cached in {@link #get}. */ - protected abstract ListenableFuture<T> compute(); + protected abstract ListenableFuture<T> compute(ProducerMonitor monitor); @Override public final ListenableFuture<T> get() { @@ -51,25 +59,12 @@ public abstract class AbstractProducer<T> implements Producer<T> { synchronized (this) { result = instance; if (result == null) { - instance = result = compute(); + ProducerMonitor monitor = monitorProvider.get().producerMonitorFor(token); + instance = result = compute(monitor); if (result == null) { throw new NullPointerException("compute returned null"); } - if (monitor != null) { - Futures.addCallback( - result, - new FutureCallback<T>() { - @Override - public void onSuccess(T value) { - monitor.succeeded(value); - } - - @Override - public void onFailure(Throwable t) { - monitor.failed(t); - } - }); - } + monitor.addCallbackTo(result); } } } diff --git a/producers/src/main/java/dagger/producers/internal/Producers.java b/producers/src/main/java/dagger/producers/internal/Producers.java index 6cd06d40e..7052c3508 100644 --- a/producers/src/main/java/dagger/producers/internal/Producers.java +++ b/producers/src/main/java/dagger/producers/internal/Producers.java @@ -20,17 +20,10 @@ import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.FutureFallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListenableFutureTask; import dagger.producers.Produced; import dagger.producers.Producer; import dagger.producers.monitoring.ProducerMonitor; -import dagger.producers.monitoring.ProducerToken; -import dagger.producers.monitoring.ProductionComponentMonitor; import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.Executor; -import java.util.concurrent.RejectedExecutionException; -import javax.annotation.Nullable; import javax.inject.Provider; import static com.google.common.base.Preconditions.checkNotNull; @@ -55,12 +48,17 @@ public final class Producers { // TODO(user): Document what happens with an InterruptedException after you figure out how to // trigger one in a test. public static <T> ListenableFuture<Produced<T>> createFutureProduced(ListenableFuture<T> future) { + // TODO(dpb): Switch to Futures.catchAsync once guava_jdk5 gets to v19. return Futures.withFallback( - Futures.transform(future, new Function<T, Produced<T>>() { - @Override public Produced<T> apply(final T value) { - return Produced.successful(value); - } - }), Producers.<T>futureFallbackForProduced()); + Futures.transform( + future, + new Function<T, Produced<T>>() { + @Override + public Produced<T> apply(final T value) { + return Produced.successful(value); + } + }), + Producers.<T>futureFallbackForProduced()); } @@ -90,19 +88,6 @@ public final class Producers { } /** - * Submits a callable to an executor, returning the future representing the task. This mirrors - * {@link com.google.common.util.concurrent.ListeningExecutorService#submit}, but only requires an - * {@link Executor}. - * - * @throws RejectedExecutionException if this task cannot be accepted for execution. - */ - public static <T> ListenableFuture<T> submitToExecutor(Callable<T> callable, Executor executor) { - ListenableFutureTask<T> future = ListenableFutureTask.create(callable); - executor.execute(future); - return future; - } - - /** * Returns a producer that immediately executes the binding logic for the given provider every * time it is called. */ @@ -110,20 +95,30 @@ public final class Producers { checkNotNull(provider); return new AbstractProducer<T>() { @Override - protected ListenableFuture<T> compute() { + protected ListenableFuture<T> compute(ProducerMonitor unusedMonitor) { return Futures.immediateFuture(provider.get()); } }; } - /** Lifts {@link ProductionComponentMonitor#producerMonitorFor} to nullable types. */ - @Nullable - public static ProducerMonitor producerMonitorFor( - @Nullable ProductionComponentMonitor componentMonitor, ProducerToken token) { - if (componentMonitor != null) { - return componentMonitor.producerMonitorFor(token); - } - return null; + /** Returns a producer that succeeds with the given value. */ + public static <T> Producer<T> immediateProducer(final T value) { + return new Producer<T>() { + @Override + public ListenableFuture<T> get() { + return Futures.immediateFuture(value); + } + }; + } + + /** Returns a producer that fails with the given exception. */ + public static <T> Producer<T> immediateFailedProducer(final Throwable throwable) { + return new Producer<T>() { + @Override + public ListenableFuture<T> get() { + return Futures.immediateFailedFuture(throwable); + } + }; } private Producers() {} diff --git a/producers/src/main/java/dagger/producers/internal/SetOfProducedProducer.java b/producers/src/main/java/dagger/producers/internal/SetOfProducedProducer.java new file mode 100644 index 000000000..e51bcad42 --- /dev/null +++ b/producers/src/main/java/dagger/producers/internal/SetOfProducedProducer.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dagger.producers.internal; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import dagger.producers.Produced; +import dagger.producers.Producer; +import dagger.producers.monitoring.ProducerMonitor; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +/** + * A {@link Producer} implementation used to implement {@link Set} bindings. This producer returns a + * future {@code Set<Produced<T>>} whose elements are populated by subsequent calls to the delegate + * {@link Producer#get} methods. + * + * @author Jesse Beder + * @since 2.0 + */ +public final class SetOfProducedProducer<T> extends AbstractProducer<Set<Produced<T>>> { + /** + * Returns a new producer that creates {@link Set} futures from the union of the given + * {@link Producer} instances. + */ + @SafeVarargs + public static <T> Producer<Set<Produced<T>>> create(Producer<Set<T>>... producers) { + return new SetOfProducedProducer<T>(ImmutableSet.copyOf(producers)); + } + + private final ImmutableSet<Producer<Set<T>>> contributingProducers; + + private SetOfProducedProducer(ImmutableSet<Producer<Set<T>>> contributingProducers) { + this.contributingProducers = contributingProducers; + } + + /** + * Returns a future {@link Set} of {@link Produced} values whose iteration order is that of the + * elements given by each of the producers, which are invoked in the order given at creation. + * + * <p>If any of the delegate sets, or any elements therein, are null, then that corresponding + * {@code Produced} element will fail with a NullPointerException. + * + * <p>Canceling this future will attempt to cancel all of the component futures; but if any of the + * delegate futures fail or are canceled, this future succeeds, with the appropriate failed + * {@link Produced}. + * + * @throws NullPointerException if any of the delegate producers return null + */ + @Override + public ListenableFuture<Set<Produced<T>>> compute(ProducerMonitor unusedMonitor) { + List<ListenableFuture<Produced<Set<T>>>> futureProducedSets = + new ArrayList<ListenableFuture<Produced<Set<T>>>>(contributingProducers.size()); + for (Producer<Set<T>> producer : contributingProducers) { + ListenableFuture<Set<T>> futureSet = producer.get(); + if (futureSet == null) { + throw new NullPointerException(producer + " returned null"); + } + futureProducedSets.add(Producers.createFutureProduced(futureSet)); + } + return Futures.transform( + Futures.allAsList(futureProducedSets), + new Function<List<Produced<Set<T>>>, Set<Produced<T>>>() { + @Override + public Set<Produced<T>> apply(List<Produced<Set<T>>> producedSets) { + ImmutableSet.Builder<Produced<T>> builder = ImmutableSet.builder(); + for (Produced<Set<T>> producedSet : producedSets) { + try { + Set<T> set = producedSet.get(); + if (set == null) { + // TODO(beder): This is a vague exception. Can we somehow point to the failing + // producer? See the similar comment in the component writer about null + // provisions. + builder.add( + Produced.<T>failed( + new NullPointerException( + "Cannot contribute a null set into a producer set binding when it's" + + " injected as Set<Produced<T>>."))); + } else { + for (T value : set) { + if (value == null) { + builder.add( + Produced.<T>failed( + new NullPointerException( + "Cannot contribute a null element into a producer set binding" + + " when it's injected as Set<Produced<T>>."))); + } else { + builder.add(Produced.successful(value)); + } + } + } + } catch (ExecutionException e) { + builder.add(Produced.<T>failed(e.getCause())); + } + } + return builder.build(); + } + }); + } +} diff --git a/producers/src/main/java/dagger/producers/internal/SetProducer.java b/producers/src/main/java/dagger/producers/internal/SetProducer.java index ba3312882..5b3c0902d 100644 --- a/producers/src/main/java/dagger/producers/internal/SetProducer.java +++ b/producers/src/main/java/dagger/producers/internal/SetProducer.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import dagger.producers.Producer; +import dagger.producers.monitoring.ProducerMonitor; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -37,8 +38,8 @@ public final class SetProducer<T> extends AbstractProducer<Set<T>> { * Returns a new producer that creates {@link Set} futures from the union of the given * {@link Producer} instances. */ - public static <T> Producer<Set<T>> create( - @SuppressWarnings("unchecked") Producer<Set<T>>... producers) { + @SafeVarargs + public static <T> Producer<Set<T>> create(Producer<Set<T>>... producers) { return new SetProducer<T>(ImmutableSet.copyOf(producers)); } @@ -62,7 +63,7 @@ public final class SetProducer<T> extends AbstractProducer<Set<T>> { * @throws NullPointerException if any of the delegate producers return null */ @Override - public ListenableFuture<Set<T>> compute() { + public ListenableFuture<Set<T>> compute(ProducerMonitor unusedMonitor) { List<ListenableFuture<Set<T>>> futureSets = new ArrayList<ListenableFuture<Set<T>>>(contributingProducers.size()); for (Producer<Set<T>> producer : contributingProducers) { diff --git a/producers/src/main/java/dagger/producers/monitoring/ProducerMonitor.java b/producers/src/main/java/dagger/producers/monitoring/ProducerMonitor.java index 8e4e9b28b..20551c3db 100644 --- a/producers/src/main/java/dagger/producers/monitoring/ProducerMonitor.java +++ b/producers/src/main/java/dagger/producers/monitoring/ProducerMonitor.java @@ -15,27 +15,37 @@ */ package dagger.producers.monitoring; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import dagger.producers.Produces; + /** * A hook for monitoring the execution of individual {@linkplain Produces producer methods}. See * {@link ProductionComponentMonitor} for how to install these monitors. * - * <p>The lifecycle of the monitor is: + * <p>The lifecycle of the monitor, under normal conditions, is: * <ul> - * <li>{@link #methodStarting} + * <li>{@link #methodStarting()} * <li>The method is called - * <li>{@link #methodFinished} + * <li>{@link #methodFinished()} * <li>If the method returns a value, then: * <ul> - * <li>{#succeeded} if the method returned normally; or - * <li>{#failed} if the method threw an exception. + * <li>{@link #succeeded(Object)} if the method returned normally; or + * <li>{@link #failed(Throwable)} if the method threw an exception. * </ul> * <li>If the method returns a future, then: * <ul> - * <li>{#succeeded} if the method returned normally, and the future succeeded; or - * <li>{#failed} if the method threw an exception, or returned normally and the future failed. + * <li>{@link #succeeded(Object)} if the method returned normally, and the future succeeded; or + * <li>{@link #failed(Throwable)} if the method threw an exception, or returned normally and the + * future failed. * </ul> * </ul> * + * <p>If any input to the monitored producer fails, {@link #failed(Throwable)} will be called + * immediately with the failed input's exception. If more than one input fails, an arbitrary failed + * input's exception is used. + * * <p>If any of the monitor's methods throw, then the exception will be logged and processing will * continue unaffected. * @@ -45,33 +55,52 @@ public abstract class ProducerMonitor { /** * Called when the producer method is about to start executing. * - * <p>When multiple monitors are installed, the order that each monitor will call - * {@code methodWillStart} is unspecified, but will remain consistent throughout the course of the - * execution of a component. + * <p>When multiple monitors are installed, the order that each monitor will call this method is + * unspecified, but will remain consistent throughout the course of the execution of a component. */ public void methodStarting() {} /** * Called when the producer method has finished executing. * - * <p>When multiple monitors are installed, the {@code methodFinished} calls will be in the - * reverse order from the {@link #methodWillStart} calls. + * <p>When multiple monitors are installed, calls to this method will be in the reverse order from + * calls to {@link #methodStarting()}. */ public void methodFinished() {} /** * Called when the producer’s future has completed successfully with a value. * - * <p>When multiple monitors are installed, the {@code futureSucceeded} calls will be in the - * reverse order from the {@link #methodWillStart} calls. + * <p>When multiple monitors are installed, calls to this method will be in the reverse order from + * calls to {@link #methodStarting()}. */ public void succeeded(Object o) {} /** * Called when the producer's future has failed with an exception. * - * <p>When multiple monitors are installed, the {@code futureFailed} calls will be in the reverse - * order from the {@link #methodWillStart} calls. + * <p>When multiple monitors are installed, calls to this method will be in the reverse order from + * calls to {@link #methodStarting()}. */ public void failed(Throwable t) {} + + /** + * Adds this monitor's completion methods as a callback to the future. This is only intended to be + * overridden in the framework! + */ + public <T> void addCallbackTo(ListenableFuture<T> future) { + Futures.addCallback( + future, + new FutureCallback<T>() { + @Override + public void onSuccess(T value) { + succeeded(value); + } + + @Override + public void onFailure(Throwable t) { + failed(t); + } + }); + } } diff --git a/producers/src/main/java/dagger/producers/monitoring/ProducerToken.java b/producers/src/main/java/dagger/producers/monitoring/ProducerToken.java index 126a40d4e..5834206ee 100644 --- a/producers/src/main/java/dagger/producers/monitoring/ProducerToken.java +++ b/producers/src/main/java/dagger/producers/monitoring/ProducerToken.java @@ -15,6 +15,8 @@ */ package dagger.producers.monitoring; +import dagger.producers.Produces; + import static com.google.common.base.Preconditions.checkNotNull; /** A token that represents an individual {@linkplain Produces producer method}. */ diff --git a/producers/src/main/java/dagger/producers/monitoring/ProductionComponentMonitor.java b/producers/src/main/java/dagger/producers/monitoring/ProductionComponentMonitor.java index 1a62dfa1f..4dc2903fa 100644 --- a/producers/src/main/java/dagger/producers/monitoring/ProductionComponentMonitor.java +++ b/producers/src/main/java/dagger/producers/monitoring/ProductionComponentMonitor.java @@ -15,6 +15,9 @@ */ package dagger.producers.monitoring; +import dagger.producers.Produces; +import dagger.producers.ProductionComponent; + /** * A hook for monitoring execution of {@linkplain ProductionComponent production components}. To * install a {@code ProductionComponentMonitor}, contribute to a set binding of diff --git a/producers/src/main/java/dagger/producers/monitoring/internal/MonitorCache.java b/producers/src/main/java/dagger/producers/monitoring/internal/MonitorCache.java new file mode 100644 index 000000000..2a76c5f71 --- /dev/null +++ b/producers/src/main/java/dagger/producers/monitoring/internal/MonitorCache.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dagger.producers.monitoring.internal; + +import dagger.producers.monitoring.ProductionComponentMonitor; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.inject.Provider; + +/** + * A class that provides a {@link ProductionComponentMonitor} for use in production components. + * + * <p>This caches the underlying the monitor, since we want a single instance for each component. + */ +public final class MonitorCache { + private static final Logger logger = Logger.getLogger(MonitorCache.class.getName()); + + private volatile ProductionComponentMonitor monitor; + + /** + * Returns the underlying monitor. This will only actually compute the monitor the first time it + * is called; subsequent calls will simply return the cached value, so the arguments to this + * method are ignored. It is expected (though not checked) that this method is called with + * equivalent arguments each time (like a {@link dagger.Provides @Provides} method would). + */ + public ProductionComponentMonitor monitor( + Provider<?> componentProvider, + Provider<Set<ProductionComponentMonitor.Factory>> monitorFactorySetProvider) { + ProductionComponentMonitor result = monitor; + if (result == null) { + synchronized (this) { + result = monitor; + if (result == null) { + try { + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + monitorFactorySetProvider.get()); + result = monitor = factory.create(componentProvider.get()); + } catch (RuntimeException e) { + logger.log(Level.SEVERE, "RuntimeException while constructing monitor factories.", e); + result = monitor = Monitors.noOpProductionComponentMonitor(); + } + } + } + } + return result; + } +} diff --git a/producers/src/main/java/dagger/producers/monitoring/internal/Monitors.java b/producers/src/main/java/dagger/producers/monitoring/internal/Monitors.java new file mode 100644 index 000000000..f27ce37b1 --- /dev/null +++ b/producers/src/main/java/dagger/producers/monitoring/internal/Monitors.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2015 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dagger.producers.monitoring.internal; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.ListenableFuture; +import dagger.producers.monitoring.ProducerMonitor; +import dagger.producers.monitoring.ProducerToken; +import dagger.producers.monitoring.ProductionComponentMonitor; +import java.util.Collection; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.inject.Provider; + +/** + * Utility methods relating to monitoring, for use in generated producers code. + * + * @author Jesse Beder + */ +public final class Monitors { + private static final Logger logger = Logger.getLogger(Monitors.class.getName()); + + /** + * Returns a monitor factory that delegates to the given factories, and ensures that any method + * called on this object, even transitively, does not throw a {@link RuntimeException} or return + * null. + * + * <p>If the delegate monitors throw an {@link Error}, then that will escape this monitor + * implementation. Errors are treated as unrecoverable conditions, and may cause the entire + * component's execution to fail. + */ + public static ProductionComponentMonitor.Factory delegatingProductionComponentMonitorFactory( + Collection<? extends ProductionComponentMonitor.Factory> factories) { + if (factories.isEmpty()) { + return noOpProductionComponentMonitorFactory(); + } else if (factories.size() == 1) { + return new NonThrowingProductionComponentMonitor.Factory(Iterables.getOnlyElement(factories)); + } else { + return new DelegatingProductionComponentMonitor.Factory(factories); + } + } + + /** + * A component monitor that delegates to a single monitor, and catches and logs all exceptions + * that the delegate throws. + */ + private static final class NonThrowingProductionComponentMonitor + implements ProductionComponentMonitor { + private final ProductionComponentMonitor delegate; + + NonThrowingProductionComponentMonitor(ProductionComponentMonitor delegate) { + this.delegate = delegate; + } + + @Override + public ProducerMonitor producerMonitorFor(ProducerToken token) { + try { + ProducerMonitor monitor = delegate.producerMonitorFor(token); + return monitor == null ? noOpProducerMonitor() : new NonThrowingProducerMonitor(monitor); + } catch (RuntimeException e) { + logProducerMonitorForException(e, delegate, token); + return noOpProducerMonitor(); + } + } + + static final class Factory implements ProductionComponentMonitor.Factory { + private final ProductionComponentMonitor.Factory delegate; + + Factory(ProductionComponentMonitor.Factory delegate) { + this.delegate = delegate; + } + + @Override + public ProductionComponentMonitor create(Object component) { + try { + ProductionComponentMonitor monitor = delegate.create(component); + return monitor == null + ? noOpProductionComponentMonitor() + : new NonThrowingProductionComponentMonitor(monitor); + } catch (RuntimeException e) { + logCreateException(e, delegate, component); + return noOpProductionComponentMonitor(); + } + } + } + } + + /** + * A producer monitor that delegates to a single monitor, and catches and logs all exceptions + * that the delegate throws. + */ + private static final class NonThrowingProducerMonitor extends ProducerMonitor { + private final ProducerMonitor delegate; + + NonThrowingProducerMonitor(ProducerMonitor delegate) { + this.delegate = delegate; + } + + @Override + public void methodStarting() { + try { + delegate.methodStarting(); + } catch (RuntimeException e) { + logProducerMonitorMethodException(e, delegate, "methodStarting"); + } + } + + @Override + public void methodFinished() { + try { + delegate.methodFinished(); + } catch (RuntimeException e) { + logProducerMonitorMethodException(e, delegate, "methodFinished"); + } + } + + @Override + public void succeeded(Object o) { + try { + delegate.succeeded(o); + } catch (RuntimeException e) { + logProducerMonitorArgMethodException(e, delegate, "succeeded", o); + } + } + + @Override + public void failed(Throwable t) { + try { + delegate.failed(t); + } catch (RuntimeException e) { + logProducerMonitorArgMethodException(e, delegate, "failed", t); + } + } + } + + /** + * A component monitor that delegates to several monitors, and catches and logs all exceptions + * that the delegates throw. + */ + private static final class DelegatingProductionComponentMonitor + implements ProductionComponentMonitor { + private final ImmutableList<ProductionComponentMonitor> delegates; + + DelegatingProductionComponentMonitor(ImmutableList<ProductionComponentMonitor> delegates) { + this.delegates = delegates; + } + + @Override + public ProducerMonitor producerMonitorFor(ProducerToken token) { + ImmutableList.Builder<ProducerMonitor> monitorsBuilder = ImmutableList.builder(); + for (ProductionComponentMonitor delegate : delegates) { + try { + ProducerMonitor monitor = delegate.producerMonitorFor(token); + if (monitor != null) { + monitorsBuilder.add(monitor); + } + } catch (RuntimeException e) { + logProducerMonitorForException(e, delegate, token); + } + } + ImmutableList<ProducerMonitor> monitors = monitorsBuilder.build(); + if (monitors.isEmpty()) { + return noOpProducerMonitor(); + } else if (monitors.size() == 1) { + return new NonThrowingProducerMonitor(Iterables.getOnlyElement(monitors)); + } else { + return new DelegatingProducerMonitor(monitors); + } + } + + static final class Factory implements ProductionComponentMonitor.Factory { + private final ImmutableList<? extends ProductionComponentMonitor.Factory> delegates; + + Factory(Iterable<? extends ProductionComponentMonitor.Factory> delegates) { + this.delegates = ImmutableList.copyOf(delegates); + } + + @Override + public ProductionComponentMonitor create(Object component) { + ImmutableList.Builder<ProductionComponentMonitor> monitorsBuilder = ImmutableList.builder(); + for (ProductionComponentMonitor.Factory delegate : delegates) { + try { + ProductionComponentMonitor monitor = delegate.create(component); + if (monitor != null) { + monitorsBuilder.add(monitor); + } + } catch (RuntimeException e) { + logCreateException(e, delegate, component); + } + } + ImmutableList<ProductionComponentMonitor> monitors = monitorsBuilder.build(); + if (monitors.isEmpty()) { + return noOpProductionComponentMonitor(); + } else if (monitors.size() == 1) { + return new NonThrowingProductionComponentMonitor(Iterables.getOnlyElement(monitors)); + } else { + return new DelegatingProductionComponentMonitor(monitors); + } + } + } + } + + /** + * A producer monitor that delegates to several monitors, and catches and logs all exceptions + * that the delegates throw. + */ + private static final class DelegatingProducerMonitor extends ProducerMonitor { + private final ImmutableList<ProducerMonitor> delegates; + + DelegatingProducerMonitor(ImmutableList<ProducerMonitor> delegates) { + this.delegates = delegates; + } + + @Override + public void methodStarting() { + for (ProducerMonitor delegate : delegates) { + try { + delegate.methodStarting(); + } catch (RuntimeException e) { + logProducerMonitorMethodException(e, delegate, "methodStarting"); + } + } + } + + @Override + public void methodFinished() { + for (ProducerMonitor delegate : delegates.reverse()) { + try { + delegate.methodFinished(); + } catch (RuntimeException e) { + logProducerMonitorMethodException(e, delegate, "methodFinished"); + } + } + } + + @Override + public void succeeded(Object o) { + for (ProducerMonitor delegate : delegates.reverse()) { + try { + delegate.succeeded(o); + } catch (RuntimeException e) { + logProducerMonitorArgMethodException(e, delegate, "succeeded", o); + } + } + } + + @Override + public void failed(Throwable t) { + for (ProducerMonitor delegate : delegates.reverse()) { + try { + delegate.failed(t); + } catch (RuntimeException e) { + logProducerMonitorArgMethodException(e, delegate, "failed", t); + } + } + } + } + + /** Returns a monitor factory that returns no-op component monitors. */ + public static ProductionComponentMonitor.Factory noOpProductionComponentMonitorFactory() { + return NO_OP_PRODUCTION_COMPONENT_MONITOR_FACTORY; + } + + /** Returns a component monitor that returns no-op producer monitors. */ + public static ProductionComponentMonitor noOpProductionComponentMonitor() { + return NO_OP_PRODUCTION_COMPONENT_MONITOR; + } + + /** Returns a producer monitor that does nothing. */ + public static ProducerMonitor noOpProducerMonitor() { + return NO_OP_PRODUCER_MONITOR; + } + + /** Returns a provider of a no-op component monitor. */ + public static Provider<ProductionComponentMonitor> noOpProductionComponentMonitorProvider() { + return NO_OP_PRODUCTION_COMPONENT_MONITOR_PROVIDER; + } + + private static final ProductionComponentMonitor.Factory + NO_OP_PRODUCTION_COMPONENT_MONITOR_FACTORY = + new ProductionComponentMonitor.Factory() { + @Override + public ProductionComponentMonitor create(Object component) { + return noOpProductionComponentMonitor(); + } + }; + + private static final ProductionComponentMonitor NO_OP_PRODUCTION_COMPONENT_MONITOR = + new ProductionComponentMonitor() { + @Override + public ProducerMonitor producerMonitorFor(ProducerToken token) { + return noOpProducerMonitor(); + } + }; + + private static final ProducerMonitor NO_OP_PRODUCER_MONITOR = + new ProducerMonitor() { + @Override + public <T> void addCallbackTo(ListenableFuture<T> future) { + // overridden to avoid adding a do-nothing callback + } + }; + + private static final Provider<ProductionComponentMonitor> + NO_OP_PRODUCTION_COMPONENT_MONITOR_PROVIDER = + new Provider() { + @Override + public ProductionComponentMonitor get() { + return noOpProductionComponentMonitor(); + } + }; + + private static void logCreateException( + RuntimeException e, ProductionComponentMonitor.Factory factory, Object component) { + logger.log( + Level.SEVERE, + "RuntimeException while calling ProductionComponentMonitor.Factory.create on factory " + + factory + + " with component " + + component, + e); + } + + private static void logProducerMonitorForException( + RuntimeException e, ProductionComponentMonitor monitor, ProducerToken token) { + logger.log( + Level.SEVERE, + "RuntimeException while calling ProductionComponentMonitor.producerMonitorFor on monitor " + + monitor + + " with token " + + token, + e); + } + + private static void logProducerMonitorMethodException( + RuntimeException e, ProducerMonitor monitor, String method) { + logger.log( + Level.SEVERE, + "RuntimeException while calling ProducerMonitor." + method + " on monitor " + monitor, + e); + } + + private static void logProducerMonitorArgMethodException( + RuntimeException e, ProducerMonitor monitor, String method, Object arg) { + logger.log( + Level.SEVERE, + "RuntimeException while calling ProducerMonitor." + + method + + " on monitor " + + monitor + + " with " + + arg, + e); + } + + private Monitors() {} +} diff --git a/producers/src/test/java/dagger/producers/internal/AbstractProducerTest.java b/producers/src/test/java/dagger/producers/internal/AbstractProducerTest.java index 15a64bd1f..923ea70a1 100644 --- a/producers/src/test/java/dagger/producers/internal/AbstractProducerTest.java +++ b/producers/src/test/java/dagger/producers/internal/AbstractProducerTest.java @@ -20,35 +20,51 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import dagger.producers.Producer; import dagger.producers.monitoring.ProducerMonitor; +import dagger.producers.monitoring.ProducerToken; +import dagger.producers.monitoring.ProductionComponentMonitor; import java.util.concurrent.ExecutionException; +import javax.inject.Provider; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; /** * Tests {@link AbstractProducer}. */ @RunWith(JUnit4.class) public class AbstractProducerTest { - @Mock private ProducerMonitor monitor; + @Mock private ProductionComponentMonitor componentMonitor; + private ProducerMonitor monitor; + private Provider<ProductionComponentMonitor> componentMonitorProvider; @Before public void initMocks() { MockitoAnnotations.initMocks(this); + monitor = Mockito.mock(ProducerMonitor.class, Mockito.CALLS_REAL_METHODS); + when(componentMonitor.producerMonitorFor(any(ProducerToken.class))).thenReturn(monitor); + componentMonitorProvider = + new Provider<ProductionComponentMonitor>() { + @Override + public ProductionComponentMonitor get() { + return componentMonitor; + } + }; } @Test public void get_nullPointerException() { - Producer<Object> producer = new DelegateProducer<>(monitor, null); + Producer<Object> producer = new DelegateProducer<>(componentMonitorProvider, null); try { producer.get(); fail(); @@ -58,11 +74,11 @@ public class AbstractProducerTest { @Test public void get() throws Exception { Producer<Integer> producer = - new AbstractProducer<Integer>(monitor) { + new AbstractProducer<Integer>(componentMonitorProvider, null) { int i = 0; @Override - public ListenableFuture<Integer> compute() { + public ListenableFuture<Integer> compute(ProducerMonitor unusedMonitor) { return Futures.immediateFuture(i++); } }; @@ -74,11 +90,11 @@ public class AbstractProducerTest { @Test public void monitor_success() throws Exception { SettableFuture<Integer> delegateFuture = SettableFuture.create(); - Producer<Integer> producer = new DelegateProducer<>(monitor, delegateFuture); + Producer<Integer> producer = new DelegateProducer<>(componentMonitorProvider, delegateFuture); ListenableFuture<Integer> future = producer.get(); assertThat(future.isDone()).isFalse(); - verifyZeroInteractions(monitor); + verify(monitor).addCallbackTo(any(ListenableFuture.class)); delegateFuture.set(-42); assertThat(future.get()).isEqualTo(-42); verify(monitor).succeeded(-42); @@ -88,11 +104,11 @@ public class AbstractProducerTest { @Test public void monitor_failure() throws Exception { SettableFuture<Integer> delegateFuture = SettableFuture.create(); - Producer<Integer> producer = new DelegateProducer<>(monitor, delegateFuture); + Producer<Integer> producer = new DelegateProducer<>(componentMonitorProvider, delegateFuture); ListenableFuture<Integer> future = producer.get(); assertThat(future.isDone()).isFalse(); - verifyZeroInteractions(monitor); + verify(monitor).addCallbackTo(any(ListenableFuture.class)); Throwable t = new RuntimeException("monkey"); delegateFuture.setException(t); try { @@ -105,22 +121,23 @@ public class AbstractProducerTest { verifyNoMoreInteractions(monitor); } - @Test + @Test(expected = NullPointerException.class) public void monitor_null() throws Exception { - Producer<Integer> producer = new DelegateProducer<>(null, Futures.immediateFuture(42)); - assertThat(producer.get().get()).isEqualTo(42); + new DelegateProducer<>(null, Futures.immediateFuture(42)); } static final class DelegateProducer<T> extends AbstractProducer<T> { private final ListenableFuture<T> delegate; - DelegateProducer(ProducerMonitor monitor, ListenableFuture<T> delegate) { - super(monitor); + DelegateProducer( + Provider<ProductionComponentMonitor> componentMonitorProvider, + ListenableFuture<T> delegate) { + super(componentMonitorProvider, null); this.delegate = delegate; } @Override - public ListenableFuture<T> compute() { + public ListenableFuture<T> compute(ProducerMonitor unusedMonitor) { return delegate; } } diff --git a/producers/src/test/java/dagger/producers/internal/ProducersTest.java b/producers/src/test/java/dagger/producers/internal/ProducersTest.java index 43564089f..e2707b342 100644 --- a/producers/src/test/java/dagger/producers/internal/ProducersTest.java +++ b/producers/src/test/java/dagger/producers/internal/ProducersTest.java @@ -17,12 +17,10 @@ package dagger.producers.internal; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import dagger.producers.Produced; import dagger.producers.Producer; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import javax.inject.Provider; @@ -98,16 +96,6 @@ public class ProducersTest { } } - @Test public void submitToExecutor() throws Exception { - ListenableFuture<Integer> future = Producers.submitToExecutor(new Callable<Integer>() { - @Override public Integer call() { - return 42; - } - }, MoreExecutors.directExecutor()); - assertThat(future.isDone()).isTrue(); - assertThat(future.get()).isEqualTo(42); - } - @Test public void producerFromProvider() throws Exception { Producer<Integer> producer = Producers.producerFromProvider(new Provider<Integer>() { int i = 0; diff --git a/producers/src/test/java/dagger/producers/internal/SetOfProducedProducerTest.java b/producers/src/test/java/dagger/producers/internal/SetOfProducedProducerTest.java new file mode 100644 index 000000000..e36ba05a4 --- /dev/null +++ b/producers/src/test/java/dagger/producers/internal/SetOfProducedProducerTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dagger.producers.internal; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import dagger.producers.Produced; +import dagger.producers.Producer; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static com.google.common.truth.Truth.assertThat; + +/** + * Tests {@link SetOfProducedProducer}. + */ +@RunWith(JUnit4.class) +public class SetOfProducedProducerTest { + @Test + public void success() throws Exception { + Producer<Set<Produced<Integer>>> producer = + SetOfProducedProducer.create( + Producers.<Set<Integer>>immediateProducer(ImmutableSet.of(1, 2)), + Producers.<Set<Integer>>immediateProducer(ImmutableSet.of(5, 7))); + assertThat(producer.get().get()) + .containsExactly( + Produced.successful(1), + Produced.successful(2), + Produced.successful(5), + Produced.successful(7)); + } + + @Test + public void failure() throws Exception { + RuntimeException e = new RuntimeException("monkey"); + Producer<Set<Produced<Integer>>> producer = + SetOfProducedProducer.create( + Producers.<Set<Integer>>immediateProducer(ImmutableSet.of(1, 2)), + Producers.<Set<Integer>>immediateFailedProducer(e)); + assertThat(producer.get().get()) + .containsExactly( + Produced.successful(1), Produced.successful(2), Produced.<Integer>failed(e)); + } + + @Test + public void delegateSetNpe() throws Exception { + Producer<Set<Produced<Integer>>> producer = + SetOfProducedProducer.create(Producers.<Set<Integer>>immediateProducer(null)); + Results<Integer> results = Results.create(producer.get().get()); + assertThat(results.successes).isEmpty(); + assertThat(results.failures).hasSize(1); + assertThat(Iterables.getOnlyElement(results.failures).getCause()) + .isInstanceOf(NullPointerException.class); + } + + @Test + public void oneOfDelegateSetNpe() throws Exception { + Producer<Set<Produced<Integer>>> producer = + SetOfProducedProducer.create( + Producers.<Set<Integer>>immediateProducer(null), + Producers.<Set<Integer>>immediateProducer(ImmutableSet.of(7, 3))); + Results<Integer> results = Results.create(producer.get().get()); + assertThat(results.successes).containsExactly(3, 7); + assertThat(results.failures).hasSize(1); + assertThat(Iterables.getOnlyElement(results.failures).getCause()) + .isInstanceOf(NullPointerException.class); + } + + @Test + public void delegateElementNpe() throws Exception { + Producer<Set<Produced<Integer>>> producer = + SetOfProducedProducer.create( + Producers.<Set<Integer>>immediateProducer(Collections.<Integer>singleton(null))); + Results<Integer> results = Results.create(producer.get().get()); + assertThat(results.successes).isEmpty(); + assertThat(results.failures).hasSize(1); + assertThat(Iterables.getOnlyElement(results.failures).getCause()) + .isInstanceOf(NullPointerException.class); + } + + @Test + public void oneOfDelegateElementNpe() throws Exception { + Producer<Set<Produced<Integer>>> producer = + SetOfProducedProducer.create( + Producers.<Set<Integer>>immediateProducer(Sets.newHashSet(Arrays.asList(5, 2, null)))); + Results<Integer> results = Results.create(producer.get().get()); + assertThat(results.successes).containsExactly(2, 5); + assertThat(results.failures).hasSize(1); + assertThat(Iterables.getOnlyElement(results.failures).getCause()) + .isInstanceOf(NullPointerException.class); + } + + static final class Results<T> { + final ImmutableSet<T> successes; + final ImmutableSet<ExecutionException> failures; + + private Results(ImmutableSet<T> successes, ImmutableSet<ExecutionException> failures) { + this.successes = successes; + this.failures = failures; + } + + static <T> Results<T> create(Set<Produced<T>> setOfProduced) { + ImmutableSet.Builder<T> successes = ImmutableSet.builder(); + ImmutableSet.Builder<ExecutionException> failures = ImmutableSet.builder(); + for (Produced<T> produced : setOfProduced) { + try { + successes.add(produced.get()); + } catch (ExecutionException e) { + failures.add(e); + } + } + return new Results<T>(successes.build(), failures.build()); + } + } +} diff --git a/producers/src/test/java/dagger/producers/internal/SetProducerTest.java b/producers/src/test/java/dagger/producers/internal/SetProducerTest.java index 1f8ff7c3a..a38830f0a 100644 --- a/producers/src/test/java/dagger/producers/internal/SetProducerTest.java +++ b/producers/src/test/java/dagger/producers/internal/SetProducerTest.java @@ -16,7 +16,6 @@ package dagger.producers.internal; import com.google.common.collect.ImmutableSet; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import dagger.producers.Producer; import java.util.Collections; @@ -35,16 +34,18 @@ import static org.junit.Assert.fail; @RunWith(JUnit4.class) public class SetProducerTest { @Test public void success() throws Exception { - Producer<Set<Integer>> producer = SetProducer.create( - new ImmediateProducer<Set<Integer>>(ImmutableSet.of(1, 2)), - new ImmediateProducer<Set<Integer>>(ImmutableSet.of(5, 7))); + Producer<Set<Integer>> producer = + SetProducer.create( + Producers.<Set<Integer>>immediateProducer(ImmutableSet.of(1, 2)), + Producers.<Set<Integer>>immediateProducer(ImmutableSet.of(5, 7))); assertThat(producer.get().get()).containsExactly(1, 2, 5, 7); } @Test public void delegateSetNpe() throws Exception { - Producer<Set<Integer>> producer = SetProducer.create( - new ImmediateProducer<Set<Integer>>(ImmutableSet.of(1, 2)), - new ImmediateProducer<Set<Integer>>(null)); + Producer<Set<Integer>> producer = + SetProducer.create( + Producers.<Set<Integer>>immediateProducer(ImmutableSet.of(1, 2)), + Producers.<Set<Integer>>immediateProducer(null)); ListenableFuture<Set<Integer>> future = producer.get(); try { future.get(); @@ -55,10 +56,10 @@ public class SetProducerTest { } @Test public void delegateElementNpe() throws Exception { - Producer<Set<Integer>> producer = SetProducer.create( - new ImmediateProducer<Set<Integer>>(ImmutableSet.of(1, 2)), - new ImmediateProducer<Set<Integer>>( - Collections.<Integer>singleton(null))); + Producer<Set<Integer>> producer = + SetProducer.create( + Producers.<Set<Integer>>immediateProducer(ImmutableSet.of(1, 2)), + Producers.<Set<Integer>>immediateProducer(Collections.<Integer>singleton(null))); ListenableFuture<Set<Integer>> future = producer.get(); try { future.get(); @@ -67,16 +68,4 @@ public class SetProducerTest { assertThat(e.getCause()).isInstanceOf(NullPointerException.class); } } - - private static final class ImmediateProducer<T> implements Producer<T> { - private final T value; - - ImmediateProducer(T value) { - this.value = value; - } - - @Override public ListenableFuture<T> get() { - return Futures.immediateFuture(value); - } - } } diff --git a/producers/src/test/java/dagger/producers/monitoring/internal/MonitorsTest.java b/producers/src/test/java/dagger/producers/monitoring/internal/MonitorsTest.java new file mode 100644 index 000000000..e7f42746b --- /dev/null +++ b/producers/src/test/java/dagger/producers/monitoring/internal/MonitorsTest.java @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2015 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dagger.producers.monitoring.internal; + +import com.google.common.collect.ImmutableList; +import dagger.producers.monitoring.ProducerMonitor; +import dagger.producers.monitoring.ProducerToken; +import dagger.producers.monitoring.ProductionComponentMonitor; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@RunWith(JUnit4.class) +public final class MonitorsTest { + @Mock private ProductionComponentMonitor.Factory mockProductionComponentMonitorFactory; + @Mock private ProductionComponentMonitor mockProductionComponentMonitor; + @Mock private ProducerMonitor mockProducerMonitor; + @Mock private ProductionComponentMonitor.Factory mockProductionComponentMonitorFactoryA; + @Mock private ProductionComponentMonitor.Factory mockProductionComponentMonitorFactoryB; + @Mock private ProductionComponentMonitor.Factory mockProductionComponentMonitorFactoryC; + @Mock private ProductionComponentMonitor mockProductionComponentMonitorA; + @Mock private ProductionComponentMonitor mockProductionComponentMonitorB; + @Mock private ProductionComponentMonitor mockProductionComponentMonitorC; + @Mock private ProducerMonitor mockProducerMonitorA; + @Mock private ProducerMonitor mockProducerMonitorB; + @Mock private ProducerMonitor mockProducerMonitorC; + + @Before + public void initMocks() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void zeroMonitorsReturnsNoOp() { + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.<ProductionComponentMonitor.Factory>of()); + assertThat(factory).isSameAs(Monitors.noOpProductionComponentMonitorFactory()); + } + + @Test + public void singleMonitor_nullProductionComponentMonitor() { + when(mockProductionComponentMonitorFactory.create(any(Object.class))).thenReturn(null); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of(mockProductionComponentMonitorFactory)); + assertThat(factory.create(new Object())).isSameAs(Monitors.noOpProductionComponentMonitor()); + } + + @Test + public void singleMonitor_throwingProductionComponentMonitorFactory() { + doThrow(new RuntimeException("monkey")) + .when(mockProductionComponentMonitorFactory) + .create(any(Object.class)); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of(mockProductionComponentMonitorFactory)); + assertThat(factory.create(new Object())).isSameAs(Monitors.noOpProductionComponentMonitor()); + } + + @Test + public void singleMonitor_nullProducerMonitor() { + when(mockProductionComponentMonitorFactory.create(any(Object.class))) + .thenReturn(mockProductionComponentMonitor); + when(mockProductionComponentMonitor.producerMonitorFor(any(ProducerToken.class))) + .thenReturn(null); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of(mockProductionComponentMonitorFactory)); + ProductionComponentMonitor monitor = factory.create(new Object()); + assertThat(monitor.producerMonitorFor(ProducerToken.create(Object.class))) + .isSameAs(Monitors.noOpProducerMonitor()); + } + + @Test + public void singleMonitor_throwingProductionComponentMonitor() { + when(mockProductionComponentMonitorFactory.create(any(Object.class))) + .thenReturn(mockProductionComponentMonitor); + doThrow(new RuntimeException("monkey")) + .when(mockProductionComponentMonitor) + .producerMonitorFor(any(ProducerToken.class)); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of(mockProductionComponentMonitorFactory)); + ProductionComponentMonitor monitor = factory.create(new Object()); + assertThat(monitor.producerMonitorFor(ProducerToken.create(Object.class))) + .isSameAs(Monitors.noOpProducerMonitor()); + } + + @Test + public void singleMonitor_normalProducerMonitorSuccess() { + setUpNormalSingleMonitor(); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of(mockProductionComponentMonitorFactory)); + ProductionComponentMonitor monitor = factory.create(new Object()); + ProducerMonitor producerMonitor = + monitor.producerMonitorFor(ProducerToken.create(Object.class)); + Object o = new Object(); + producerMonitor.methodStarting(); + producerMonitor.methodFinished(); + producerMonitor.succeeded(o); + + InOrder order = inOrder(mockProducerMonitor); + order.verify(mockProducerMonitor).methodStarting(); + order.verify(mockProducerMonitor).methodFinished(); + order.verify(mockProducerMonitor).succeeded(o); + verifyNoMoreInteractions(mockProducerMonitor); + } + + @Test + public void singleMonitor_normalProducerMonitorFailure() { + setUpNormalSingleMonitor(); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of(mockProductionComponentMonitorFactory)); + ProductionComponentMonitor monitor = factory.create(new Object()); + ProducerMonitor producerMonitor = + monitor.producerMonitorFor(ProducerToken.create(Object.class)); + Throwable t = new RuntimeException("monkey"); + producerMonitor.methodStarting(); + producerMonitor.methodFinished(); + producerMonitor.failed(t); + + InOrder order = inOrder(mockProducerMonitor); + order.verify(mockProducerMonitor).methodStarting(); + order.verify(mockProducerMonitor).methodFinished(); + order.verify(mockProducerMonitor).failed(t); + verifyNoMoreInteractions(mockProducerMonitor); + } + + @Test + public void singleMonitor_throwingProducerMonitorSuccess() { + setUpNormalSingleMonitor(); + doThrow(new RuntimeException("monkey")).when(mockProducerMonitor).methodStarting(); + doThrow(new RuntimeException("monkey")).when(mockProducerMonitor).methodFinished(); + doThrow(new RuntimeException("monkey")).when(mockProducerMonitor).succeeded(any(Object.class)); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of(mockProductionComponentMonitorFactory)); + ProductionComponentMonitor monitor = factory.create(new Object()); + ProducerMonitor producerMonitor = + monitor.producerMonitorFor(ProducerToken.create(Object.class)); + Object o = new Object(); + producerMonitor.methodStarting(); + producerMonitor.methodFinished(); + producerMonitor.succeeded(o); + + InOrder order = inOrder(mockProducerMonitor); + order.verify(mockProducerMonitor).methodStarting(); + order.verify(mockProducerMonitor).methodFinished(); + order.verify(mockProducerMonitor).succeeded(o); + verifyNoMoreInteractions(mockProducerMonitor); + } + + @Test + public void singleMonitor_throwingProducerMonitorFailure() { + setUpNormalSingleMonitor(); + doThrow(new RuntimeException("monkey")).when(mockProducerMonitor).methodStarting(); + doThrow(new RuntimeException("monkey")).when(mockProducerMonitor).methodFinished(); + doThrow(new RuntimeException("monkey")).when(mockProducerMonitor).failed(any(Throwable.class)); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of(mockProductionComponentMonitorFactory)); + ProductionComponentMonitor monitor = factory.create(new Object()); + ProducerMonitor producerMonitor = + monitor.producerMonitorFor(ProducerToken.create(Object.class)); + Throwable t = new RuntimeException("gorilla"); + producerMonitor.methodStarting(); + producerMonitor.methodFinished(); + producerMonitor.failed(t); + + InOrder order = inOrder(mockProducerMonitor); + order.verify(mockProducerMonitor).methodStarting(); + order.verify(mockProducerMonitor).methodFinished(); + order.verify(mockProducerMonitor).failed(t); + verifyNoMoreInteractions(mockProducerMonitor); + } + + @Test + public void multipleMonitors_nullProductionComponentMonitors() { + when(mockProductionComponentMonitorFactoryA.create(any(Object.class))).thenReturn(null); + when(mockProductionComponentMonitorFactoryB.create(any(Object.class))).thenReturn(null); + when(mockProductionComponentMonitorFactoryC.create(any(Object.class))).thenReturn(null); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of( + mockProductionComponentMonitorFactoryA, + mockProductionComponentMonitorFactoryB, + mockProductionComponentMonitorFactoryC)); + assertThat(factory.create(new Object())).isSameAs(Monitors.noOpProductionComponentMonitor()); + } + + @Test + public void multipleMonitors_throwingProductionComponentMonitorFactories() { + doThrow(new RuntimeException("monkey")) + .when(mockProductionComponentMonitorFactoryA) + .create(any(Object.class)); + doThrow(new RuntimeException("monkey")) + .when(mockProductionComponentMonitorFactoryB) + .create(any(Object.class)); + doThrow(new RuntimeException("monkey")) + .when(mockProductionComponentMonitorFactoryC) + .create(any(Object.class)); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of( + mockProductionComponentMonitorFactoryA, + mockProductionComponentMonitorFactoryB, + mockProductionComponentMonitorFactoryC)); + assertThat(factory.create(new Object())).isSameAs(Monitors.noOpProductionComponentMonitor()); + } + + @Test + public void multipleMonitors_someNullProductionComponentMonitors() { + when(mockProductionComponentMonitorFactoryA.create(any(Object.class))) + .thenReturn(mockProductionComponentMonitorA); + when(mockProductionComponentMonitorFactoryB.create(any(Object.class))).thenReturn(null); + when(mockProductionComponentMonitorFactoryC.create(any(Object.class))).thenReturn(null); + when(mockProductionComponentMonitorA.producerMonitorFor(any(ProducerToken.class))) + .thenReturn(mockProducerMonitorA); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of( + mockProductionComponentMonitorFactoryA, + mockProductionComponentMonitorFactoryB, + mockProductionComponentMonitorFactoryC)); + ProductionComponentMonitor monitor = factory.create(new Object()); + ProducerMonitor producerMonitor = + monitor.producerMonitorFor(ProducerToken.create(Object.class)); + + Object o = new Object(); + producerMonitor.methodStarting(); + producerMonitor.methodFinished(); + producerMonitor.succeeded(o); + + InOrder order = inOrder(mockProducerMonitorA); + order.verify(mockProducerMonitorA).methodStarting(); + order.verify(mockProducerMonitorA).methodFinished(); + order.verify(mockProducerMonitorA).succeeded(o); + verifyNoMoreInteractions(mockProducerMonitorA); + } + + @Test + public void multipleMonitors_someThrowingProductionComponentMonitorFactories() { + when(mockProductionComponentMonitorFactoryA.create(any(Object.class))) + .thenReturn(mockProductionComponentMonitorA); + doThrow(new RuntimeException("monkey")) + .when(mockProductionComponentMonitorFactoryB) + .create(any(Object.class)); + doThrow(new RuntimeException("monkey")) + .when(mockProductionComponentMonitorFactoryC) + .create(any(Object.class)); + when(mockProductionComponentMonitorA.producerMonitorFor(any(ProducerToken.class))) + .thenReturn(mockProducerMonitorA); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of( + mockProductionComponentMonitorFactoryA, + mockProductionComponentMonitorFactoryB, + mockProductionComponentMonitorFactoryC)); + ProductionComponentMonitor monitor = factory.create(new Object()); + ProducerMonitor producerMonitor = + monitor.producerMonitorFor(ProducerToken.create(Object.class)); + + Object o = new Object(); + producerMonitor.methodStarting(); + producerMonitor.methodFinished(); + producerMonitor.succeeded(o); + + InOrder order = inOrder(mockProducerMonitorA); + order.verify(mockProducerMonitorA).methodStarting(); + order.verify(mockProducerMonitorA).methodFinished(); + order.verify(mockProducerMonitorA).succeeded(o); + verifyNoMoreInteractions(mockProducerMonitorA); + } + + @Test + public void multipleMonitors_normalProductionComponentMonitorSuccess() { + setUpNormalMultipleMonitors(); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of( + mockProductionComponentMonitorFactoryA, + mockProductionComponentMonitorFactoryB, + mockProductionComponentMonitorFactoryC)); + ProductionComponentMonitor monitor = factory.create(new Object()); + ProducerMonitor producerMonitor = + monitor.producerMonitorFor(ProducerToken.create(Object.class)); + + Object o = new Object(); + producerMonitor.methodStarting(); + producerMonitor.methodFinished(); + producerMonitor.succeeded(o); + + InOrder order = inOrder(mockProducerMonitorA, mockProducerMonitorB, mockProducerMonitorC); + order.verify(mockProducerMonitorA).methodStarting(); + order.verify(mockProducerMonitorB).methodStarting(); + order.verify(mockProducerMonitorC).methodStarting(); + order.verify(mockProducerMonitorC).methodFinished(); + order.verify(mockProducerMonitorB).methodFinished(); + order.verify(mockProducerMonitorA).methodFinished(); + order.verify(mockProducerMonitorC).succeeded(o); + order.verify(mockProducerMonitorB).succeeded(o); + order.verify(mockProducerMonitorA).succeeded(o); + verifyNoMoreInteractions(mockProducerMonitorA, mockProducerMonitorB, mockProducerMonitorC); + } + + @Test + public void multipleMonitors_normalProductionComponentMonitorFailure() { + setUpNormalMultipleMonitors(); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of( + mockProductionComponentMonitorFactoryA, + mockProductionComponentMonitorFactoryB, + mockProductionComponentMonitorFactoryC)); + ProductionComponentMonitor monitor = factory.create(new Object()); + ProducerMonitor producerMonitor = + monitor.producerMonitorFor(ProducerToken.create(Object.class)); + + Throwable t = new RuntimeException("chimpanzee"); + producerMonitor.methodStarting(); + producerMonitor.methodFinished(); + producerMonitor.failed(t); + + InOrder order = inOrder(mockProducerMonitorA, mockProducerMonitorB, mockProducerMonitorC); + order.verify(mockProducerMonitorA).methodStarting(); + order.verify(mockProducerMonitorB).methodStarting(); + order.verify(mockProducerMonitorC).methodStarting(); + order.verify(mockProducerMonitorC).methodFinished(); + order.verify(mockProducerMonitorB).methodFinished(); + order.verify(mockProducerMonitorA).methodFinished(); + order.verify(mockProducerMonitorC).failed(t); + order.verify(mockProducerMonitorB).failed(t); + order.verify(mockProducerMonitorA).failed(t); + verifyNoMoreInteractions(mockProducerMonitorA, mockProducerMonitorB, mockProducerMonitorC); + } + + @Test + public void multipleMonitors_someThrowingProducerMonitorsSuccess() { + setUpNormalMultipleMonitors(); + doThrow(new RuntimeException("monkey")).when(mockProducerMonitorA).methodStarting(); + doThrow(new RuntimeException("monkey")).when(mockProducerMonitorB).methodFinished(); + doThrow(new RuntimeException("monkey")).when(mockProducerMonitorC).succeeded(any(Object.class)); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of( + mockProductionComponentMonitorFactoryA, + mockProductionComponentMonitorFactoryB, + mockProductionComponentMonitorFactoryC)); + ProductionComponentMonitor monitor = factory.create(new Object()); + ProducerMonitor producerMonitor = + monitor.producerMonitorFor(ProducerToken.create(Object.class)); + + Object o = new Object(); + producerMonitor.methodStarting(); + producerMonitor.methodFinished(); + producerMonitor.succeeded(o); + + InOrder order = inOrder(mockProducerMonitorA, mockProducerMonitorB, mockProducerMonitorC); + order.verify(mockProducerMonitorA).methodStarting(); + order.verify(mockProducerMonitorB).methodStarting(); + order.verify(mockProducerMonitorC).methodStarting(); + order.verify(mockProducerMonitorC).methodFinished(); + order.verify(mockProducerMonitorB).methodFinished(); + order.verify(mockProducerMonitorA).methodFinished(); + order.verify(mockProducerMonitorC).succeeded(o); + order.verify(mockProducerMonitorB).succeeded(o); + order.verify(mockProducerMonitorA).succeeded(o); + verifyNoMoreInteractions(mockProducerMonitorA, mockProducerMonitorB, mockProducerMonitorC); + } + + @Test + public void multipleMonitors_someThrowingProducerMonitorsFailure() { + setUpNormalMultipleMonitors(); + doThrow(new RuntimeException("monkey")).when(mockProducerMonitorA).methodStarting(); + doThrow(new RuntimeException("monkey")).when(mockProducerMonitorB).methodFinished(); + doThrow(new RuntimeException("monkey")).when(mockProducerMonitorC).failed(any(Throwable.class)); + ProductionComponentMonitor.Factory factory = + Monitors.delegatingProductionComponentMonitorFactory( + ImmutableList.of( + mockProductionComponentMonitorFactoryA, + mockProductionComponentMonitorFactoryB, + mockProductionComponentMonitorFactoryC)); + ProductionComponentMonitor monitor = factory.create(new Object()); + ProducerMonitor producerMonitor = + monitor.producerMonitorFor(ProducerToken.create(Object.class)); + + Throwable t = new RuntimeException("chimpanzee"); + producerMonitor.methodStarting(); + producerMonitor.methodFinished(); + producerMonitor.failed(t); + + InOrder order = inOrder(mockProducerMonitorA, mockProducerMonitorB, mockProducerMonitorC); + order.verify(mockProducerMonitorA).methodStarting(); + order.verify(mockProducerMonitorB).methodStarting(); + order.verify(mockProducerMonitorC).methodStarting(); + order.verify(mockProducerMonitorC).methodFinished(); + order.verify(mockProducerMonitorB).methodFinished(); + order.verify(mockProducerMonitorA).methodFinished(); + order.verify(mockProducerMonitorC).failed(t); + order.verify(mockProducerMonitorB).failed(t); + order.verify(mockProducerMonitorA).failed(t); + verifyNoMoreInteractions(mockProducerMonitorA, mockProducerMonitorB, mockProducerMonitorC); + } + + private void setUpNormalSingleMonitor() { + when(mockProductionComponentMonitorFactory.create(any(Object.class))) + .thenReturn(mockProductionComponentMonitor); + when(mockProductionComponentMonitor.producerMonitorFor(any(ProducerToken.class))) + .thenReturn(mockProducerMonitor); + } + + private void setUpNormalMultipleMonitors() { + when(mockProductionComponentMonitorFactoryA.create(any(Object.class))) + .thenReturn(mockProductionComponentMonitorA); + when(mockProductionComponentMonitorFactoryB.create(any(Object.class))) + .thenReturn(mockProductionComponentMonitorB); + when(mockProductionComponentMonitorFactoryC.create(any(Object.class))) + .thenReturn(mockProductionComponentMonitorC); + when(mockProductionComponentMonitorA.producerMonitorFor(any(ProducerToken.class))) + .thenReturn(mockProducerMonitorA); + when(mockProductionComponentMonitorB.producerMonitorFor(any(ProducerToken.class))) + .thenReturn(mockProducerMonitorB); + when(mockProductionComponentMonitorC.producerMonitorFor(any(ProducerToken.class))) + .thenReturn(mockProducerMonitorC); + } +} diff --git a/resources/META-INF/services/javax.annotation.processing.Processor b/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 000000000..069807e64 --- /dev/null +++ b/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +dagger.internal.codegen.ComponentProcessor |