[Ahead-of-time Subcomponents] Support for Optional bindings. This includes
support for replacing private binding methods with public modifiable binding
methods, and wrapping all modifiable bindings not already wrapped by a
modifiable binding method.
This also includes a consolidation of the functional tests.
RELNOTES=n/a
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=211634962
diff --git a/java/dagger/internal/codegen/BUILD b/java/dagger/internal/codegen/BUILD
index eac3f02..baf685e 100644
--- a/java/dagger/internal/codegen/BUILD
+++ b/java/dagger/internal/codegen/BUILD
@@ -227,7 +227,6 @@
java_library(
name = "writing",
srcs = [
- "AbstractMethodModifiableBindingExpression.java",
"AnnotationCreatorGenerator.java",
"BindingExpression.java",
"BindingMethodImplementation.java",
@@ -267,8 +266,10 @@
"MembersInjectorProviderCreationExpression.java",
"MethodBindingExpression.java",
"MissingBindingExpression.java",
+ "ModifiableAbstractMethodBindingExpression.java",
"ModifiableBindingMethods.java",
"ModifiableBindingType.java",
+ "ModifiableConcreteMethodBindingExpression.java",
"MonitoringModuleGenerator.java",
"MonitoringModuleProcessingStep.java",
"OptionalBindingExpression.java",
diff --git a/java/dagger/internal/codegen/BindingExpression.java b/java/dagger/internal/codegen/BindingExpression.java
index b15852a..1f190f6 100644
--- a/java/dagger/internal/codegen/BindingExpression.java
+++ b/java/dagger/internal/codegen/BindingExpression.java
@@ -19,6 +19,7 @@
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
+import dagger.internal.codegen.ModifiableBindingMethods.ModifiableBindingMethod;
/** A factory of code expressions used to access a single request for a binding in a component. */
// TODO(user): Rename this to RequestExpression?
@@ -47,4 +48,13 @@
// By default, just delegate to #getDependencyExpression().
return CodeBlock.of("return $L;", getDependencyExpression(component.name()).codeBlock());
}
+
+ /**
+ * Returns an expression for the implementation of a modifiable binding method for the given
+ * component model.
+ */
+ CodeBlock getModifiableBindingMethodImplementation(
+ ModifiableBindingMethod modifiableBindingMethod, GeneratedComponentModel component) {
+ return CodeBlock.of("return $L;", getDependencyExpression(component.name()).codeBlock());
+ }
}
diff --git a/java/dagger/internal/codegen/ComponentBindingExpressions.java b/java/dagger/internal/codegen/ComponentBindingExpressions.java
index 8f4b5b3..beed699 100644
--- a/java/dagger/internal/codegen/ComponentBindingExpressions.java
+++ b/java/dagger/internal/codegen/ComponentBindingExpressions.java
@@ -43,6 +43,7 @@
import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
import dagger.internal.codegen.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
import dagger.internal.codegen.ModifiableBindingMethods.ModifiableBindingMethod;
+import dagger.model.BindingKind;
import dagger.model.DependencyRequest;
import dagger.model.Key;
import dagger.model.RequestKind;
@@ -246,18 +247,27 @@
Optional<MethodSpec> getComponentMethod(ComponentMethodDescriptor componentMethod) {
checkArgument(componentMethod.dependencyRequest().isPresent());
DependencyRequest dependencyRequest = componentMethod.dependencyRequest().get();
- MethodSpec.Builder methodBuilder =
+ MethodSpec method =
MethodSpec.overriding(
- componentMethod.methodElement(),
- MoreTypes.asDeclared(graph.componentType().asType()),
- types);
+ componentMethod.methodElement(),
+ MoreTypes.asDeclared(graph.componentType().asType()),
+ types)
+ .addCode(
+ getBindingExpression(dependencyRequest.key(), dependencyRequest.kind())
+ .getComponentMethodImplementation(componentMethod, generatedComponentModel))
+ .build();
- ModifiableBindingType type =
+ ModifiableBindingType modifiableBindingType =
getModifiableBindingType(dependencyRequest.key(), dependencyRequest.kind());
- if (type.isModifiable()) {
+ if (modifiableBindingType.isModifiable()) {
generatedComponentModel.registerModifiableBindingMethod(
- type, dependencyRequest.key(), dependencyRequest.kind(), methodBuilder.build());
- if (!type.hasBaseClassImplementation()) {
+ modifiableBindingType,
+ dependencyRequest.key(),
+ dependencyRequest.kind(),
+ method,
+ newModifiableBindingWillBeFinalized(
+ modifiableBindingType, dependencyRequest.key(), dependencyRequest.kind()));
+ if (!modifiableBindingType.hasBaseClassImplementation()) {
// A component method should not be emitted if it encapsulates a modifiable binding that
// cannot be satisfied by the abstract base class implementation of a subcomponent.
checkState(
@@ -268,39 +278,71 @@
}
}
- return Optional.of(
- methodBuilder
- .addCode(
- getBindingExpression(dependencyRequest.key(), dependencyRequest.kind())
- .getComponentMethodImplementation(componentMethod, generatedComponentModel))
- .build());
+ return Optional.of(method);
}
/**
- * Returns the implementation of a method encapsulating a modifiable binding in a supertype
+ * Returns the implementation of a modifiable binding method originally defined in a supertype
* implementation of this subcomponent. Returns {@link Optional#empty()} when the binding cannot
* or should not be modified by the current binding graph. This is only relevant for ahead-of-time
* subcomponents.
*/
- Optional<MethodSpec> getModifiableBindingMethod(ModifiableBindingMethod modifiableBindingMethod) {
- if (shouldOverrideModifiableBindingMethod(modifiableBindingMethod)) {
- Expression bindingExpression =
- getDependencyExpression(
- modifiableBindingMethod.key(),
- modifiableBindingMethod.kind(),
- generatedComponentModel.name());
- MethodSpec baseMethod = modifiableBindingMethod.baseMethod();
+ Optional<ModifiableBindingMethod> getModifiableBindingMethod(
+ ModifiableBindingMethod modifiableBindingMethod) {
+ if (shouldModifyKnownBinding(modifiableBindingMethod)) {
+ Key key = modifiableBindingMethod.key();
+ RequestKind requestKind = modifiableBindingMethod.kind();
+ MethodSpec baseMethod = modifiableBindingMethod.methodSpec();
return Optional.of(
- MethodSpec.methodBuilder(baseMethod.name)
- .addModifiers(PUBLIC)
- .returns(baseMethod.returnType)
- .addAnnotation(Override.class)
- .addStatement("return $L", bindingExpression.codeBlock())
- .build());
+ ModifiableBindingMethod.implement(
+ modifiableBindingMethod,
+ MethodSpec.methodBuilder(baseMethod.name)
+ .addModifiers(PUBLIC)
+ .returns(baseMethod.returnType)
+ .addAnnotation(Override.class)
+ .addCode(
+ getBindingExpression(key, requestKind)
+ .getModifiableBindingMethodImplementation(
+ modifiableBindingMethod, generatedComponentModel))
+ .build(),
+ knownModifiableBindingWillBeFinalized(modifiableBindingMethod)));
}
return Optional.empty();
}
+ /**
+ * Returns true if a modifiable binding method that was registered in a superclass implementation
+ * of this subcomponent should be marked as "finalized" if it is being overridden by this
+ * subcomponent implementation. "Finalized" means we should not attempt to modify the binding in
+ * any subcomponent subclass. This is only relevant for ahead-of-time subcomponents.
+ */
+ // TODO(user): extract a ModifiableBindingExpressions class? This may need some dependencies
+ // (like the GCM) but could remove some concerns from this class
+ private boolean knownModifiableBindingWillBeFinalized(
+ ModifiableBindingMethod modifiableBindingMethod) {
+ ModifiableBindingType newModifiableBindingType =
+ getModifiableBindingType(modifiableBindingMethod.key(), modifiableBindingMethod.kind());
+ if (!newModifiableBindingType.isModifiable()) {
+ // If a modifiable binding has become non-modifiable it is final by definition.
+ return true;
+ }
+ // All currently supported modifiable types are finalized upon modification.
+ return shouldModifyBinding(
+ newModifiableBindingType, modifiableBindingMethod.key(), modifiableBindingMethod.kind());
+ }
+
+ /**
+ * Returns true if a newly discovered modifiable binding method, once it is defined in this
+ * subcomponent implementation, should be marked as "finalized", meaning we should not attempt to
+ * modify the binding in any subcomponent subclass. This is only relevant for ahead-of-time
+ * subcomponents.
+ */
+ private boolean newModifiableBindingWillBeFinalized(
+ ModifiableBindingType modifiableBindingType, Key key, RequestKind requestKind) {
+ // All currently supported modifiable types are finalized upon modification.
+ return shouldModifyBinding(modifiableBindingType, key, requestKind);
+ }
+
private BindingExpression getBindingExpression(Key key, RequestKind requestKind) {
if (expressions.contains(key, requestKind)) {
return expressions.get(key, requestKind);
@@ -352,13 +394,34 @@
*/
private BindingExpression createModifiableBindingExpression(
ModifiableBindingType type, Key key, RequestKind requestKind) {
+ ResolvedBindings resolvedBindings = graph.resolvedBindings(requestKind, key);
+ Optional<ModifiableBindingMethod> matchingModifiableBindingMethod =
+ generatedComponentModel.getModifiableBindingMethod(key, requestKind);
+ Optional<ComponentMethodDescriptor> matchingComponentMethod =
+ findMatchingComponentMethod(key, requestKind);
switch (type) {
case GENERATED_INSTANCE:
- ResolvedBindings resolvedBindings = graph.resolvedBindings(requestKind, key);
return new GeneratedInstanceBindingExpression(
- generatedComponentModel, resolvedBindings, requestKind);
+ generatedComponentModel,
+ resolvedBindings,
+ requestKind,
+ matchingModifiableBindingMethod,
+ matchingComponentMethod);
case MISSING:
- return new MissingBindingExpression(generatedComponentModel, key, requestKind);
+ return new MissingBindingExpression(
+ generatedComponentModel,
+ key,
+ requestKind,
+ matchingModifiableBindingMethod,
+ matchingComponentMethod);
+ case OPTIONAL:
+ BindingExpression expression = createBindingExpression(resolvedBindings, requestKind);
+ // If the expression hasn't already been registered as a modifiable binding method then wrap
+ // the binding here.
+ if (!generatedComponentModel.getModifiableBindingMethod(key, requestKind).isPresent()) {
+ return wrapInMethod(resolvedBindings, requestKind, expression);
+ }
+ return expression;
default:
throw new IllegalStateException(
String.format(
@@ -393,6 +456,10 @@
if (binding.requiresGeneratedInstance()) {
return ModifiableBindingType.GENERATED_INSTANCE;
}
+
+ if (binding.kind().equals(BindingKind.OPTIONAL)) {
+ return ModifiableBindingType.OPTIONAL;
+ }
} else if (!resolvableBinding(key, requestKind)) {
return ModifiableBindingType.MISSING;
}
@@ -403,11 +470,29 @@
/**
* Returns true if the current binding graph can, and should, modify a binding by overriding a
- * modfiable binding method. This is only relevant for ahead-of-time subcomponents.
+ * modifiable binding method. This is only relevant for ahead-of-time subcomponents.
*/
- private boolean shouldOverrideModifiableBindingMethod(
- ModifiableBindingMethod modifiableBindingMethod) {
- switch (modifiableBindingMethod.type()) {
+ private boolean shouldModifyKnownBinding(ModifiableBindingMethod modifiableBindingMethod) {
+ ModifiableBindingType newModifiableBindingType =
+ getModifiableBindingType(modifiableBindingMethod.key(), modifiableBindingMethod.kind());
+ if (!newModifiableBindingType.equals(modifiableBindingMethod.type())) {
+ // It is possible that a binding can change types, in which case we should always modify the
+ // binding.
+ return true;
+ }
+ return shouldModifyBinding(
+ modifiableBindingMethod.type(),
+ modifiableBindingMethod.key(),
+ modifiableBindingMethod.kind());
+ }
+
+ /**
+ * Returns true if the current binding graph can, and should, modify a binding by overriding a
+ * modifiable binding method. This is only relevant for ahead-of-time subcomponents.
+ */
+ private boolean shouldModifyBinding(
+ ModifiableBindingType modifiableBindingType, Key key, RequestKind requestKind) {
+ switch (modifiableBindingType) {
case GENERATED_INSTANCE:
return !generatedComponentModel.isAbstract();
case MISSING:
@@ -415,12 +500,16 @@
// 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
- return resolvableBinding(modifiableBindingMethod.key(), modifiableBindingMethod.kind());
+ return resolvableBinding(key, requestKind);
+ case OPTIONAL:
+ // Only override optional binding methods if we have a non-empty binding.
+ ResolvedBindings resolvedBindings = graph.resolvedBindings(requestKind, key);
+ return !resolvedBindings.contributionBinding().dependencies().isEmpty();
default:
throw new IllegalStateException(
String.format(
"Overriding modifiable binding method with unsupported ModifiableBindingType [%s].",
- modifiableBindingMethod.type()));
+ modifiableBindingType));
}
}
@@ -764,10 +853,10 @@
/**
* Returns {@code true} if the binding should use the static factory creation strategy.
*
- * In default mode, we always use the static factory creation strategy. In fastInit mode, we
+ * <p>In default mode, we always use the static factory creation strategy. In fastInit mode, we
* prefer to use a SwitchingProvider instead of static factories in order to reduce class loading;
- * however, we allow static factories that can reused across multiple bindings, e.g.
- * {@code MapFactory} or {@code SetFactory}.
+ * however, we allow static factories that can reused across multiple bindings, e.g. {@code
+ * MapFactory} or {@code SetFactory}.
*/
private boolean useStaticFactoryCreation(ContributionBinding binding) {
return !(compilerOptions.experimentalAndroidMode2() || compilerOptions.fastInit())
@@ -791,8 +880,10 @@
/**
* Returns a binding expression that uses a given one as the body of a method that users call. If
- * a component provision method matches it, it will be the method implemented. If not, a new
- * private method will be written.
+ * a component provision method matches it, it will be the method implemented. If it does not
+ * match a component provision method and the binding is modifiable the a new public modifiable
+ * binding method will be written. If the binding doesn't match a component method nor is it
+ * modifiable, then a new private method will be written.
*/
private BindingExpression wrapInMethod(
ResolvedBindings resolvedBindings,
@@ -800,8 +891,24 @@
BindingExpression bindingExpression) {
BindingMethodImplementation methodImplementation =
methodImplementation(resolvedBindings, requestKind, bindingExpression);
+ Optional<ComponentMethodDescriptor> matchingComponentMethod =
+ findMatchingComponentMethod(resolvedBindings.key(), requestKind);
- return findMatchingComponentMethod(resolvedBindings.key(), requestKind)
+ ModifiableBindingType modifiableBindingType =
+ getModifiableBindingType(resolvedBindings.key(), requestKind);
+ if (modifiableBindingType.isModifiable() && !matchingComponentMethod.isPresent()) {
+ return new ModifiableConcreteMethodBindingExpression(
+ resolvedBindings,
+ requestKind,
+ modifiableBindingType,
+ methodImplementation,
+ generatedComponentModel,
+ generatedComponentModel.getModifiableBindingMethod(resolvedBindings.key(), requestKind),
+ newModifiableBindingWillBeFinalized(
+ modifiableBindingType, resolvedBindings.key(), requestKind));
+ }
+
+ return matchingComponentMethod
.<BindingExpression>map(
componentMethod ->
new ComponentMethodBindingExpression(
diff --git a/java/dagger/internal/codegen/ComponentModelBuilder.java b/java/dagger/internal/codegen/ComponentModelBuilder.java
index d3a3872..54b4fc9 100644
--- a/java/dagger/internal/codegen/ComponentModelBuilder.java
+++ b/java/dagger/internal/codegen/ComponentModelBuilder.java
@@ -317,6 +317,8 @@
private void addSubcomponents() {
for (BindingGraph subgraph : graph.subgraphs()) {
+ // TODO(b/72748365): Can an abstract inner subcomponent implementation be elided if it's
+ // totally empty?
generatedComponentModel.addSubcomponent(
subgraph.componentDescriptor(),
generatedComponentModel.isAbstract()
@@ -596,9 +598,7 @@
bindingExpressions
.getModifiableBindingMethod(modifiableBindingMethod)
.ifPresent(
- method ->
- generatedComponentModel.addImplementedModifiableBindingMethod(
- modifiableBindingMethod, method));
+ method -> generatedComponentModel.addImplementedModifiableBindingMethod(method));
}
} else {
super.addInterfaceMethods();
diff --git a/java/dagger/internal/codegen/GeneratedComponentModel.java b/java/dagger/internal/codegen/GeneratedComponentModel.java
index 6b21c5e..67cd215 100644
--- a/java/dagger/internal/codegen/GeneratedComponentModel.java
+++ b/java/dagger/internal/codegen/GeneratedComponentModel.java
@@ -263,8 +263,12 @@
* ahead-of-time subcomponents.
*/
void addModifiableBindingMethod(
- ModifiableBindingType type, Key key, RequestKind kind, MethodSpec methodSpec) {
- modifiableBindingMethods.addMethod(type, key, kind, methodSpec);
+ ModifiableBindingType type,
+ Key key,
+ RequestKind kind,
+ MethodSpec methodSpec,
+ boolean finalized) {
+ modifiableBindingMethods.addMethod(type, key, kind, methodSpec, finalized);
methodSpecsMap.put(MethodSpecKind.MODIFIABLE_BINDING_METHOD, methodSpec);
}
@@ -274,15 +278,18 @@
* method, encapsulates a modifiable binding.
*/
void registerModifiableBindingMethod(
- ModifiableBindingType type, Key key, RequestKind kind, MethodSpec methodSpec) {
- modifiableBindingMethods.addMethod(type, key, kind, methodSpec);
+ ModifiableBindingType type,
+ Key key,
+ RequestKind kind,
+ MethodSpec methodSpec,
+ boolean finalized) {
+ modifiableBindingMethods.addMethod(type, key, kind, methodSpec, finalized);
}
/** Adds the implementation for the given {@link ModifiableBindingMethod} to the component. */
- void addImplementedModifiableBindingMethod(
- ModifiableBindingMethod method, MethodSpec methodSpec) {
+ void addImplementedModifiableBindingMethod(ModifiableBindingMethod method) {
modifiableBindingMethods.methodImplemented(method);
- methodSpecsMap.put(MethodSpecKind.MODIFIABLE_BINDING_METHOD, methodSpec);
+ methodSpecsMap.put(MethodSpecKind.MODIFIABLE_BINDING_METHOD, method.methodSpec());
}
/** Adds the given type to the component. */
@@ -358,13 +365,25 @@
ImmutableList<ModifiableBindingMethod> superclassModifiableBindingMethods =
supermodel.get().getModifiableBindingMethods();
superclassModifiableBindingMethods.stream()
- .filter(method -> !modifiableBindingMethods.isFinalized(method))
+ .filter(method -> !modifiableBindingMethods.finalized(method))
.forEach(modifiableBindingMethodsBuilder::add);
}
- modifiableBindingMethodsBuilder.addAll(modifiableBindingMethods.getMethods());
+ modifiableBindingMethodsBuilder.addAll(modifiableBindingMethods.getNonFinalizedMethods());
return modifiableBindingMethodsBuilder.build();
}
+ /**
+ * Returns the {@link ModifiableBindingMethod} for this subcomponent for the given binding, if it
+ * exists.
+ */
+ Optional<ModifiableBindingMethod> getModifiableBindingMethod(Key key, RequestKind requestKind) {
+ Optional<ModifiableBindingMethod> method = modifiableBindingMethods.getMethod(key, requestKind);
+ if (!method.isPresent() && supermodel.isPresent()) {
+ return supermodel.get().getModifiableBindingMethod(key, requestKind);
+ }
+ return method;
+ }
+
/** 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/GeneratedInstanceBindingExpression.java b/java/dagger/internal/codegen/GeneratedInstanceBindingExpression.java
index dffa052..90aa7c4 100644
--- a/java/dagger/internal/codegen/GeneratedInstanceBindingExpression.java
+++ b/java/dagger/internal/codegen/GeneratedInstanceBindingExpression.java
@@ -16,16 +16,19 @@
package dagger.internal.codegen;
+import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
+import dagger.internal.codegen.ModifiableBindingMethods.ModifiableBindingMethod;
import dagger.model.RequestKind;
+import java.util.Optional;
/**
- * An {@link AbstractMethodModifiableBindingExpression} for a binding that requires an instance of a
+ * An {@link ModifiableAbstractMethodBindingExpression} for a binding that requires an instance of a
* generated type. This expression is used in abstract implementations of a subcomponent when there
* are no concrete definitions of generated types available. The (unimplemented) method is added to
* the {@code GeneratedComponentModel} when this dependency expression is requested. The method is
* overridden when generating the concrete implementation of an ancestor component.
*/
-final class GeneratedInstanceBindingExpression extends AbstractMethodModifiableBindingExpression {
+final class GeneratedInstanceBindingExpression extends ModifiableAbstractMethodBindingExpression {
private final GeneratedComponentModel generatedComponentModel;
private final ContributionBinding binding;
private final RequestKind requestKind;
@@ -33,12 +36,16 @@
GeneratedInstanceBindingExpression(
GeneratedComponentModel generatedComponentModel,
ResolvedBindings resolvedBindings,
- RequestKind requestKind) {
+ RequestKind requestKind,
+ Optional<ModifiableBindingMethod> matchingModifiableBindingMethod,
+ Optional<ComponentMethodDescriptor> matchingComponentMethod) {
super(
generatedComponentModel,
ModifiableBindingType.GENERATED_INSTANCE,
resolvedBindings.key(),
- requestKind);
+ requestKind,
+ matchingModifiableBindingMethod,
+ matchingComponentMethod);
this.generatedComponentModel = generatedComponentModel;
this.binding = resolvedBindings.contributionBinding();
this.requestKind = requestKind;
diff --git a/java/dagger/internal/codegen/MissingBindingExpression.java b/java/dagger/internal/codegen/MissingBindingExpression.java
index dbc95e9..fd8f9b8 100644
--- a/java/dagger/internal/codegen/MissingBindingExpression.java
+++ b/java/dagger/internal/codegen/MissingBindingExpression.java
@@ -22,23 +22,36 @@
import static dagger.internal.codegen.SourceFiles.simpleVariableName;
import com.google.auto.common.MoreTypes;
+import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
+import dagger.internal.codegen.ModifiableBindingMethods.ModifiableBindingMethod;
import dagger.model.Key;
import dagger.model.RequestKind;
+import java.util.Optional;
/**
- * A {@link AbstractMethodModifiableBindingExpression} for a binding that is missing when generating
+ * A {@link ModifiableAbstractMethodBindingExpression} for a binding that is missing when generating
* the abstract base class implementation of a subcomponent. The (unimplemented) method is added to
* the {@link GeneratedComponentModel} when the dependency expression is requested. The method is
* overridden when generating the implementation of an ancestor component.
*/
-final class MissingBindingExpression extends AbstractMethodModifiableBindingExpression {
+final class MissingBindingExpression extends ModifiableAbstractMethodBindingExpression {
private final GeneratedComponentModel generatedComponentModel;
private final Key key;
private final RequestKind kind;
MissingBindingExpression(
- GeneratedComponentModel generatedComponentModel, Key key, RequestKind kind) {
- super(generatedComponentModel, ModifiableBindingType.MISSING, key, kind);
+ GeneratedComponentModel generatedComponentModel,
+ Key key,
+ RequestKind kind,
+ Optional<ModifiableBindingMethod> matchingModifiableBindingMethod,
+ Optional<ComponentMethodDescriptor> matchingComponentMethod) {
+ super(
+ generatedComponentModel,
+ ModifiableBindingType.MISSING,
+ key,
+ kind,
+ matchingModifiableBindingMethod,
+ matchingComponentMethod);
this.generatedComponentModel = generatedComponentModel;
this.key = key;
this.kind = kind;
diff --git a/java/dagger/internal/codegen/AbstractMethodModifiableBindingExpression.java b/java/dagger/internal/codegen/ModifiableAbstractMethodBindingExpression.java
similarity index 62%
rename from java/dagger/internal/codegen/AbstractMethodModifiableBindingExpression.java
rename to java/dagger/internal/codegen/ModifiableAbstractMethodBindingExpression.java
index 8d1efdb..77fc1ab 100644
--- a/java/dagger/internal/codegen/AbstractMethodModifiableBindingExpression.java
+++ b/java/dagger/internal/codegen/ModifiableAbstractMethodBindingExpression.java
@@ -24,8 +24,11 @@
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
+import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
+import dagger.internal.codegen.ModifiableBindingMethods.ModifiableBindingMethod;
import dagger.model.Key;
import dagger.model.RequestKind;
+import java.util.Optional;
/**
* A {@link BindingExpression} that invokes a method that encapsulates a binding that cannot be
@@ -34,42 +37,64 @@
* expression is requested. The method is overridden when generating the implementation of an
* ancestor component.
*/
-abstract class AbstractMethodModifiableBindingExpression extends BindingExpression {
+abstract class ModifiableAbstractMethodBindingExpression extends BindingExpression {
private final GeneratedComponentModel generatedComponentModel;
private final ModifiableBindingType modifiableBindingType;
private final Key key;
private final RequestKind kind;
- private String methodName;
+ private Optional<String> methodName;
- AbstractMethodModifiableBindingExpression(
+ ModifiableAbstractMethodBindingExpression(
GeneratedComponentModel generatedComponentModel,
ModifiableBindingType modifiableBindingType,
Key key,
- RequestKind kind) {
+ RequestKind kind,
+ Optional<ModifiableBindingMethod> matchingModifiableBindingMethod,
+ Optional<ComponentMethodDescriptor> matchingComponentMethod) {
this.generatedComponentModel = generatedComponentModel;
this.modifiableBindingType = modifiableBindingType;
this.key = key;
this.kind = kind;
+ this.methodName =
+ initializeMethodName(matchingComponentMethod, matchingModifiableBindingMethod);
+ }
+
+ /**
+ * If this binding corresponds to an existing component method, or a known modifiable binding
+ * method, use them to initialize the method name, which is a signal to call the existing method
+ * rather than emit an abstract method.
+ */
+ private static Optional<String> initializeMethodName(
+ Optional<ComponentMethodDescriptor> matchingComponentMethod,
+ Optional<ModifiableBindingMethod> matchingModifiableBindingMethod) {
+ if (matchingComponentMethod.isPresent()) {
+ return Optional.of(matchingComponentMethod.get().methodElement().getSimpleName().toString());
+ }
+ if (matchingModifiableBindingMethod.isPresent()) {
+ return Optional.of(matchingModifiableBindingMethod.get().methodSpec().name);
+ }
+ return Optional.empty();
}
@Override
final Expression getDependencyExpression(ClassName requestingClass) {
addUnimplementedMethod();
- return Expression.create(key.type(), CodeBlock.of("$L()", methodName));
+ return Expression.create(key.type(), CodeBlock.of("$L()", methodName.get()));
}
private void addUnimplementedMethod() {
- if (methodName == null) {
+ if (!methodName.isPresent()) {
// Only add the method once in case of repeated references to the missing binding.
- methodName = chooseMethodName();
+ methodName = Optional.of(chooseMethodName());
generatedComponentModel.addModifiableBindingMethod(
modifiableBindingType,
key,
kind,
- MethodSpec.methodBuilder(methodName)
+ MethodSpec.methodBuilder(methodName.get())
.addModifiers(PUBLIC, ABSTRACT)
.returns(requestTypeName(kind, TypeName.get(key.type())))
- .build());
+ .build(),
+ false /* finalized */);
}
}
diff --git a/java/dagger/internal/codegen/ModifiableBindingMethods.java b/java/dagger/internal/codegen/ModifiableBindingMethods.java
index 766031b..290621e 100644
--- a/java/dagger/internal/codegen/ModifiableBindingMethods.java
+++ b/java/dagger/internal/codegen/ModifiableBindingMethods.java
@@ -16,7 +16,9 @@
package dagger.internal.codegen;
+import static com.google.common.base.Preconditions.checkArgument;
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;
@@ -26,6 +28,7 @@
import dagger.model.Key;
import dagger.model.RequestKind;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
/**
@@ -38,62 +41,73 @@
* superclasses to know what binding methods to attempt to modify.
*/
final class ModifiableBindingMethods {
- private final Map<KeyAndKind, ModifiableBindingMethod> methods = Maps.newHashMap();
+ private final Map<KeyAndKind, ModifiableBindingMethod> methods = Maps.newLinkedHashMap();
private final Set<KeyAndKind> finalizedMethods = Sets.newHashSet();
/** Register a method encapsulating a modifiable binding. */
void addMethod(
- ModifiableBindingType type, Key key, RequestKind kind, MethodSpec unimplementedMethod) {
+ ModifiableBindingType type, Key key, RequestKind kind, MethodSpec method, boolean finalized) {
+ checkArgument(type.isModifiable());
KeyAndKind keyAndKind = KeyAndKind.create(key, kind);
- checkState(
- !finalizedMethods.contains(keyAndKind),
- "Adding a modifiable binding method for a binding that has been marked as finalized for "
- + "the current subcomponent implementation. The binding is for a %s-%s of type %s.",
- key,
- kind,
- type);
- methods.put(keyAndKind, ModifiableBindingMethod.create(type, key, kind, unimplementedMethod));
+ if (finalized) {
+ finalizedMethods.add(keyAndKind);
+ }
+ methods.put(keyAndKind, ModifiableBindingMethod.create(type, key, kind, method, finalized));
}
- /** Returns all {@link ModifiableBindingMethod}s. */
- ImmutableList<ModifiableBindingMethod> getMethods() {
- // We will never add a modifiable binding method and mark it as having been finalized in the
- // same instance of ModifiableBindingMethods, so there's no need to filter `methods` by
- // `finalizedMethods`.
- return ImmutableList.copyOf(methods.values());
+ /** Returns all {@link ModifiableBindingMethod}s that have not been marked as finalized. */
+ ImmutableList<ModifiableBindingMethod> getNonFinalizedMethods() {
+ return methods.values().stream().filter(m -> !m.finalized()).collect(toImmutableList());
+ }
+
+ /** Returns the {@link ModifiableBindingMethod} for the given binding if present. */
+ Optional<ModifiableBindingMethod> getMethod(Key key, RequestKind kind) {
+ return Optional.ofNullable(methods.get(KeyAndKind.create(key, kind)));
}
/**
* Mark the {@link ModifiableBindingMethod} as having been implemented, thus modifying the
- * binding. For those bindings that are finalized when modified, mark the binding as finalized,
- * meaning it should no longer be modified.
+ * binding.
*/
void methodImplemented(ModifiableBindingMethod method) {
- if (method.type().finalizedOnModification()) {
- KeyAndKind keyAndKind = KeyAndKind.create(method.key(), method.kind());
+ if (method.finalized()) {
checkState(
- !methods.containsKey(keyAndKind),
- "Indicating a modifiable binding method is finalized when it was registered as "
- + "modifiable for the current subcomponent implementation. The binding is for a "
- + "%s-%s of type %s.",
+ finalizedMethods.add(KeyAndKind.create(method.key(), method.kind())),
+ "Implementing and finalizing a modifiable binding method that has been marked as "
+ + "finalized in the current subcomponent implementation. The binding is for a %s-%s "
+ + "of type %s.",
method.key(),
method.kind(),
method.type());
- finalizedMethods.add(keyAndKind);
}
}
/** Whether a given binding has been marked as finalized. */
- boolean isFinalized(ModifiableBindingMethod method) {
+ boolean finalized(ModifiableBindingMethod method) {
return finalizedMethods.contains(KeyAndKind.create(method.key(), method.kind()));
}
@AutoValue
abstract static class ModifiableBindingMethod {
private static ModifiableBindingMethod create(
- ModifiableBindingType type, Key key, RequestKind kind, MethodSpec unimplementedMethod) {
+ ModifiableBindingType type,
+ Key key,
+ RequestKind kind,
+ MethodSpec methodSpec,
+ boolean finalized) {
return new AutoValue_ModifiableBindingMethods_ModifiableBindingMethod(
- type, key, kind, unimplementedMethod);
+ type, key, kind, methodSpec, finalized);
+ }
+
+ /** Create a {@ModifiableBindingMethod} representing an implementation of an existing method. */
+ static ModifiableBindingMethod implement(
+ ModifiableBindingMethod unimplementedMethod, MethodSpec methodSpec, boolean finalized) {
+ return new AutoValue_ModifiableBindingMethods_ModifiableBindingMethod(
+ unimplementedMethod.type(),
+ unimplementedMethod.key(),
+ unimplementedMethod.kind(),
+ methodSpec,
+ finalized);
}
abstract ModifiableBindingType type();
@@ -102,7 +116,9 @@
abstract RequestKind kind();
- abstract MethodSpec baseMethod();
+ abstract MethodSpec methodSpec();
+
+ abstract boolean finalized();
}
@AutoValue
diff --git a/java/dagger/internal/codegen/ModifiableBindingType.java b/java/dagger/internal/codegen/ModifiableBindingType.java
index db8f723..d5e2e7c 100644
--- a/java/dagger/internal/codegen/ModifiableBindingType.java
+++ b/java/dagger/internal/codegen/ModifiableBindingType.java
@@ -72,8 +72,6 @@
INJECTION,
;
- private static final ImmutableSet<ModifiableBindingType> TYPES_FINALIZED_ON_MODIFICATION =
- ImmutableSet.of(MISSING, GENERATED_INSTANCE, OPTIONAL, INJECTION);
private static final ImmutableSet<ModifiableBindingType> TYPES_WITH_BASE_CLASS_IMPLEMENTATIONS =
ImmutableSet.of(MULTIBINDING, OPTIONAL, INJECTION);
@@ -82,16 +80,6 @@
}
/**
- * Returns true if the modifiable binding should not be further modified once it's base
- * implementation has been overridden. For example, a missing or optional binding may only be
- * satisfied once in a subcomponent implementation class hierarchy, but a multibinding may be
- * modified with every implementation of a subcomponent.
- */
- boolean finalizedOnModification() {
- return TYPES_FINALIZED_ON_MODIFICATION.contains(this);
- }
-
- /**
* Returns true if the method encapsulating the modifiable binding should have a concrete
* implementation in the abstract base class for a subcomponent.
*/
diff --git a/java/dagger/internal/codegen/ModifiableConcreteMethodBindingExpression.java b/java/dagger/internal/codegen/ModifiableConcreteMethodBindingExpression.java
new file mode 100644
index 0000000..15e3215
--- /dev/null
+++ b/java/dagger/internal/codegen/ModifiableConcreteMethodBindingExpression.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 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.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.squareup.javapoet.MethodSpec.methodBuilder;
+import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.lang.model.element.Modifier.PUBLIC;
+
+import com.squareup.javapoet.TypeName;
+import dagger.internal.codegen.ModifiableBindingMethods.ModifiableBindingMethod;
+import dagger.model.RequestKind;
+import java.util.Optional;
+
+/**
+ * A binding expression that wraps a modifiable binding expression in a public, no-arg method.
+ *
+ * <p>Dependents of this binding expression will just call the modifiable binding method.
+ */
+final class ModifiableConcreteMethodBindingExpression extends MethodBindingExpression {
+ private final ContributionBinding binding;
+ private final RequestKind requestKind;
+ private final ModifiableBindingType modifiableBindingType;
+ private final BindingMethodImplementation methodImplementation;
+ private final GeneratedComponentModel generatedComponentModel;
+ private final boolean bindingFinalized;
+ private Optional<String> methodName;
+
+ ModifiableConcreteMethodBindingExpression(
+ ResolvedBindings resolvedBindings,
+ RequestKind requestKind,
+ ModifiableBindingType modifiableBindingType,
+ BindingMethodImplementation methodImplementation,
+ GeneratedComponentModel generatedComponentModel,
+ Optional<ModifiableBindingMethod> matchingModifiableBindingMethod,
+ boolean bindingFinalized) {
+ super(methodImplementation, generatedComponentModel);
+ this.binding = resolvedBindings.contributionBinding();
+ this.requestKind = checkNotNull(requestKind);
+ this.modifiableBindingType = checkNotNull(modifiableBindingType);
+ this.methodImplementation = checkNotNull(methodImplementation);
+ this.generatedComponentModel = checkNotNull(generatedComponentModel);
+ this.bindingFinalized = bindingFinalized;
+ this.methodName =
+ matchingModifiableBindingMethod.map(modifiableMethod -> modifiableMethod.methodSpec().name);
+ }
+
+ @Override
+ protected void addMethod() {
+ // Add the modifiable binding method to the component model if we haven't already.
+ if (!methodName.isPresent()) {
+ methodName =
+ Optional.of(generatedComponentModel.getUniqueGetterMethodName(binding, requestKind));
+ generatedComponentModel.addModifiableBindingMethod(
+ modifiableBindingType,
+ binding.key(),
+ requestKind,
+ methodBuilder(methodName.get())
+ .addModifiers(bindingFinalized ? PRIVATE : PUBLIC)
+ .returns(TypeName.get(methodImplementation.returnType()))
+ .addCode(methodImplementation.body())
+ .build(),
+ bindingFinalized);
+ }
+ }
+
+ @Override
+ protected String methodName() {
+ checkState(methodName.isPresent(), "addMethod() must be called before methodName().");
+ return methodName.get();
+ }
+}