[Ahead-of-time subcomponents] Add support for missing bindings in abstract
subcomponent implementations. Wrap missing bindings in methods and implement
these methods where possible.
RELNOTES=n/a
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=208056862
diff --git a/java/dagger/internal/codegen/BUILD b/java/dagger/internal/codegen/BUILD
index 4a5a6fc..84fd17d 100644
--- a/java/dagger/internal/codegen/BUILD
+++ b/java/dagger/internal/codegen/BUILD
@@ -266,6 +266,7 @@
"MembersInjectorProviderCreationExpression.java",
"MethodBindingExpression.java",
"MissingBindingExpression.java",
+ "MissingBindingMethods.java",
"MonitoringModuleGenerator.java",
"MonitoringModuleProcessingStep.java",
"OptionalBindingExpression.java",
diff --git a/java/dagger/internal/codegen/ComponentBindingExpressions.java b/java/dagger/internal/codegen/ComponentBindingExpressions.java
index 92c0233..8ecf741 100644
--- a/java/dagger/internal/codegen/ComponentBindingExpressions.java
+++ b/java/dagger/internal/codegen/ComponentBindingExpressions.java
@@ -30,6 +30,7 @@
import static dagger.model.BindingKind.DELEGATE;
import static dagger.model.BindingKind.MULTIBOUND_MAP;
import static dagger.model.BindingKind.MULTIBOUND_SET;
+import static javax.lang.model.element.Modifier.PUBLIC;
import com.google.auto.common.MoreTypes;
import com.google.common.collect.HashBasedTable;
@@ -40,6 +41,7 @@
import com.squareup.javapoet.MethodSpec;
import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
import dagger.internal.codegen.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
+import dagger.internal.codegen.MissingBindingMethods.MissingBindingMethod;
import dagger.model.DependencyRequest;
import dagger.model.Key;
import dagger.model.RequestKind;
@@ -248,6 +250,33 @@
.build();
}
+ /**
+ * Returns the implementation of a method encapsulating a missing binding in a supertype
+ * implementation of this subcomponent. Returns {@link Optional#empty()} when the binding cannot
+ * be satisfied by the current binding graph. This is only relevant for ahead-of-time
+ * subcomponents.
+ */
+ Optional<MethodSpec> getMissingBindingMethodImplementation(MissingBindingMethod missingBinding) {
+ // TODO(b/72748365): investigate beder@'s comment about having intermediate component ancestors
+ // satisfy missing bindings of their children with their own missing binding methods so that
+ // we can minimize the cases where we need to reach into doubly-nested descendant component
+ // implementations
+ if (resolvableBinding(missingBinding.key(), missingBinding.kind())) {
+ Expression bindingExpression =
+ getDependencyExpression(
+ missingBinding.key(), missingBinding.kind(), generatedComponentModel.name());
+ MethodSpec unimplementedMethod = missingBinding.unimplementedMethod();
+ return Optional.of(
+ MethodSpec.methodBuilder(unimplementedMethod.name)
+ .addModifiers(PUBLIC)
+ .returns(unimplementedMethod.returnType)
+ .addAnnotation(Override.class)
+ .addStatement("return $L", bindingExpression.codeBlock())
+ .build());
+ }
+ return Optional.empty();
+ }
+
private BindingExpression getBindingExpression(Key key, RequestKind requestKind) {
if (expressions.contains(key, requestKind)) {
return expressions.get(key, requestKind);
@@ -257,7 +286,8 @@
ResolvedBindings resolvedBindings = graph.resolvedBindings(requestKind, key);
expression = Optional.of(createBindingExpression(resolvedBindings, requestKind));
} else if (!resolvableBinding(key, requestKind) && generatedComponentModel.isAbstract()) {
- expression = Optional.of(new MissingBindingExpression(key));
+ expression =
+ Optional.of(new MissingBindingExpression(generatedComponentModel, key, requestKind));
}
if (expression.isPresent()) {
expressions.put(key, requestKind, expression.get());
diff --git a/java/dagger/internal/codegen/ComponentModelBuilder.java b/java/dagger/internal/codegen/ComponentModelBuilder.java
index eaeb5b6..4716b3d 100644
--- a/java/dagger/internal/codegen/ComponentModelBuilder.java
+++ b/java/dagger/internal/codegen/ComponentModelBuilder.java
@@ -48,6 +48,7 @@
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeSpec;
import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
+import dagger.internal.codegen.MissingBindingMethods.MissingBindingMethod;
import java.util.List;
import java.util.Optional;
import javax.lang.model.element.ExecutableElement;
@@ -533,6 +534,7 @@
private static final class AbstractSubcomponentModelBuilder extends ComponentModelBuilder {
private final Optional<ComponentModelBuilder> parent;
private final GeneratedComponentModel generatedComponentModel;
+ private final ComponentBindingExpressions bindingExpressions;
AbstractSubcomponentModelBuilder(
Optional<ComponentModelBuilder> parent,
@@ -563,6 +565,7 @@
compilerOptions);
this.parent = parent;
this.generatedComponentModel = generatedComponentModel;
+ this.bindingExpressions = bindingExpressions;
}
@Override
@@ -582,11 +585,23 @@
@Override
protected void addInterfaceMethods() {
- if (!generatedComponentModel.supermodel().isPresent()) {
+ if (generatedComponentModel.supermodel().isPresent()) {
+ // Since we're overriding a subcomponent implementation we add to its implementation given
+ // an expanded binding graph.
+
+ // Implement missing binding methods.
+ for (MissingBindingMethod missingBindingMethod :
+ generatedComponentModel.getMissingBindingMethods()) {
+ bindingExpressions
+ .getMissingBindingMethodImplementation(missingBindingMethod)
+ .ifPresent(
+ method ->
+ generatedComponentModel.addImplementedMissingBindingMethod(
+ missingBindingMethod, method));
+ }
+ } else {
super.addInterfaceMethods();
}
- // TODO(b/72748365): Contribute to modifiable portions of subcomponent implementation for
- // inner abstract subcomponents.
}
}
diff --git a/java/dagger/internal/codegen/GeneratedComponentModel.java b/java/dagger/internal/codegen/GeneratedComponentModel.java
index 33f92ac..d0dac71 100644
--- a/java/dagger/internal/codegen/GeneratedComponentModel.java
+++ b/java/dagger/internal/codegen/GeneratedComponentModel.java
@@ -34,6 +34,9 @@
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import dagger.internal.ReferenceReleasingProviderManager;
+import dagger.internal.codegen.MissingBindingMethods.MissingBindingMethod;
+import dagger.model.Key;
+import dagger.model.RequestKind;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -95,7 +98,13 @@
MEMBERS_INJECTION_METHOD,
/** A static method that always returns an absent {@code Optional} value for the binding. */
- ABSENT_OPTIONAL_METHOD
+ ABSENT_OPTIONAL_METHOD,
+
+ /**
+ * A method encapsulating a missing binding to be overridden by a subclass when generating a
+ * component ancestor. Only relevant for ahead-of-time subcomponents.
+ */
+ MISSING_BINDING_METHOD
}
/** A type of nested class that this component model can generate. */
@@ -129,6 +138,7 @@
private final ListMultimap<TypeSpecKind, TypeSpec> typeSpecsMap =
MultimapBuilder.enumKeys(TypeSpecKind.class).arrayListValues().build();
private final List<Supplier<TypeSpec>> switchingProviderSupplier = new ArrayList<>();
+ private final MissingBindingMethods missingBindingMethods = new MissingBindingMethods();
private GeneratedComponentModel(
ClassName name,
@@ -242,6 +252,18 @@
methodSpecsMap.putAll(methodKind, methodSpecs);
}
+ /** Adds the given (abstract) method representing an encapsulated missing binding. */
+ void addUnimplementedMissingBindingMethod(Key key, RequestKind kind, MethodSpec methodSpec) {
+ missingBindingMethods.addUnimplementedMethod(key, kind, methodSpec);
+ methodSpecsMap.put(MethodSpecKind.MISSING_BINDING_METHOD, methodSpec);
+ }
+
+ /** Adds the implementation for the given {@link MissingBindingMethod}. */
+ void addImplementedMissingBindingMethod(MissingBindingMethod method, MethodSpec methodSpec) {
+ missingBindingMethods.methodImplemented(method);
+ methodSpecsMap.put(MethodSpecKind.MISSING_BINDING_METHOD, methodSpec);
+ }
+
/** Adds the given type to the component. */
void addType(TypeSpecKind typeKind, TypeSpec typeSpec) {
typeSpecsMap.put(typeKind, typeSpec);
@@ -289,6 +311,25 @@
return ImmutableList.copyOf(initializations);
}
+ /**
+ * Returns the unimplemented {@link MissingBindingMethod}s for this subcomponent implementation
+ * and its superclasses.
+ */
+ ImmutableList<MissingBindingMethod> getMissingBindingMethods() {
+ ImmutableList.Builder<MissingBindingMethod> missingBindingMethodsBuilder =
+ ImmutableList.builder();
+ if (supermodel.isPresent()) {
+ ImmutableList<MissingBindingMethod> bindingsUnsatisfiedBySuperclasses =
+ supermodel.get().getMissingBindingMethods();
+ bindingsUnsatisfiedBySuperclasses
+ .stream()
+ .filter(method -> !missingBindingMethods.isMethodImplemented(method))
+ .forEach(missingBindingMethodsBuilder::add);
+ }
+ missingBindingMethodsBuilder.addAll(missingBindingMethods.getUnimplementedMethods());
+ return missingBindingMethodsBuilder.build();
+ }
+
/** Generates the component and returns the resulting {@link TypeSpec.Builder}. */
TypeSpec.Builder generate() {
fieldSpecsMap.asMap().values().forEach(component::addFields);
diff --git a/java/dagger/internal/codegen/MissingBindingExpression.java b/java/dagger/internal/codegen/MissingBindingExpression.java
index 197c388..65eb5d9 100644
--- a/java/dagger/internal/codegen/MissingBindingExpression.java
+++ b/java/dagger/internal/codegen/MissingBindingExpression.java
@@ -16,9 +16,21 @@
package dagger.internal.codegen;
+import static com.google.common.base.CaseFormat.LOWER_CAMEL;
+import static com.google.common.base.CaseFormat.UPPER_CAMEL;
+import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
+import static dagger.internal.codegen.RequestKinds.requestTypeName;
+import static dagger.internal.codegen.SourceFiles.simpleVariableName;
+import static javax.lang.model.element.Modifier.ABSTRACT;
+import static javax.lang.model.element.Modifier.PUBLIC;
+
+import com.google.auto.common.MoreTypes;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeName;
import dagger.model.Key;
+import dagger.model.RequestKind;
/**
* A {@link BindingExpression} that invokes a method that encapsulates a binding that is missing
@@ -27,15 +39,44 @@
* requested. The method is overridden when generating the implementation of an ancestor component.
*/
final class MissingBindingExpression extends BindingExpression {
+ private final GeneratedComponentModel generatedComponentModel;
private final Key key;
+ private final RequestKind kind;
+ private String methodName;
- MissingBindingExpression(Key key) {
+ MissingBindingExpression(
+ GeneratedComponentModel generatedComponentModel, Key key, RequestKind kind) {
+ this.generatedComponentModel = generatedComponentModel;
this.key = key;
+ this.kind = kind;
}
@Override
final Expression getDependencyExpression(ClassName requestingClass) {
- // TODO(b/72748365): Implement method encapsulating binding to invoke in this expression.
- return Expression.create(key.type(), CodeBlock.of("null"));
+ addUnimplementedMethod();
+ return Expression.create(key.type(), CodeBlock.of("$L()", methodName));
+ }
+
+ private void addUnimplementedMethod() {
+ if (methodName == null) {
+ // Only add the method once in case of repeated references to the missing binding.
+ methodName = chooseMethodName();
+ generatedComponentModel.addUnimplementedMissingBindingMethod(
+ key,
+ kind,
+ MethodSpec.methodBuilder(methodName)
+ .addModifiers(PUBLIC, ABSTRACT)
+ .returns(requestTypeName(kind, TypeName.get(key.type())))
+ .build());
+ }
+ }
+
+ private String chooseMethodName() {
+ return generatedComponentModel.getUniqueMethodName(
+ "get"
+ + LOWER_CAMEL.to(UPPER_CAMEL, simpleVariableName(MoreTypes.asTypeElement(key.type())))
+ + (kind.equals(RequestKind.INSTANCE)
+ ? ""
+ : UPPER_UNDERSCORE.to(UPPER_CAMEL, kind.name())));
}
}
diff --git a/java/dagger/internal/codegen/MissingBindingMethods.java b/java/dagger/internal/codegen/MissingBindingMethods.java
new file mode 100644
index 0000000..b5678a3
--- /dev/null
+++ b/java/dagger/internal/codegen/MissingBindingMethods.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.internal.codegen;
+
+import static com.google.common.base.Preconditions.checkState;
+import static dagger.internal.codegen.DaggerStreams.toImmutableList;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.squareup.javapoet.MethodSpec;
+import dagger.model.Key;
+import dagger.model.RequestKind;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A registry for those methods which each wrap a binding that is unsatisfiable by a subcomponent in
+ * isolation, but can be satisfied by an ancestor component. This is useful when generating
+ * ahead-of-time subcomponents: An instance of this class is associated with a single subcomponent
+ * implementation. We generate an implementation of a given subcomponent once for each of it's
+ * ancestor components and for any one implementation the {@link MissingBindingMethod}s of it's
+ * superclasses tell us what missing bindings have yet to be satisfied so we can attempt to satisfy
+ * them.
+ */
+final class MissingBindingMethods {
+ private final Map<KeyAndKind, MissingBindingMethod> missingBindingMethods = Maps.newHashMap();
+ private final Set<KeyAndKind> implementedMissingBindingMethods = Sets.newHashSet();
+
+ /** Record an unimplemented method encapsulating a missing binding. */
+ void addUnimplementedMethod(Key key, RequestKind kind, MethodSpec unimplementedMethod) {
+ KeyAndKind keyAndKind = KeyAndKind.create(key, kind);
+ checkState(
+ !implementedMissingBindingMethods.contains(keyAndKind),
+ "Adding an missing binding method for a method marked as implemented for the current "
+ + "subcomponent implementation. The binding is for a %s-%s.",
+ key,
+ kind);
+ missingBindingMethods.put(
+ keyAndKind, MissingBindingMethod.create(key, kind, unimplementedMethod));
+ }
+
+ /** Returns all unimplemented {@link MissingBindingMethod}s */
+ ImmutableList<MissingBindingMethod> getUnimplementedMethods() {
+ // We will never register a binding as missing and also as implemented for the same instance of
+ // MissingBindingMethods, so there's no need to filter missingBindingMethods.
+ return missingBindingMethods.values().stream().collect(toImmutableList());
+ }
+
+ /** Mark the {@link MissingBindingMethod} as having been implemented. */
+ void methodImplemented(MissingBindingMethod method) {
+ KeyAndKind keyAndKind = KeyAndKind.create(method.key(), method.kind());
+ checkState(
+ !missingBindingMethods.containsKey(keyAndKind),
+ "Indicating a missing binding method as implemented when it was registered as missing for "
+ + "the current subcomponent implementation. The binding is for a %s-%s.",
+ method.key(),
+ method.kind());
+ implementedMissingBindingMethods.add(keyAndKind);
+ }
+
+ /** Whether a given binding has been marked as implemented. */
+ boolean isMethodImplemented(MissingBindingMethod method) {
+ return implementedMissingBindingMethods.contains(
+ KeyAndKind.create(method.key(), method.kind()));
+ }
+
+ @AutoValue
+ abstract static class MissingBindingMethod {
+ private static MissingBindingMethod create(
+ Key key, RequestKind kind, MethodSpec unimplementedMethod) {
+ return new AutoValue_MissingBindingMethods_MissingBindingMethod(
+ key, kind, unimplementedMethod);
+ }
+
+ abstract Key key();
+
+ abstract RequestKind kind();
+
+ abstract MethodSpec unimplementedMethod();
+ }
+
+ @AutoValue
+ abstract static class KeyAndKind {
+ private static KeyAndKind create(Key key, RequestKind kind) {
+ return new AutoValue_MissingBindingMethods_KeyAndKind(key, kind);
+ }
+
+ abstract Key key();
+
+ abstract RequestKind kind();
+ }
+}