| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 The Dagger Authors. |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package dagger.internal.codegen; |
| 18 | |
| ronshapiro | db0d66b | 2018-11-19 12:16:43 -0800 | [diff] [blame] | 19 | import static com.google.common.base.Preconditions.checkState; |
| ronshapiro | 28f06f2 | 2018-11-12 14:03:15 -0800 | [diff] [blame] | 20 | import static com.google.common.collect.Iterables.getOnlyElement; |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 21 | import static dagger.internal.codegen.BindingRequest.bindingRequest; |
| ronshapiro | a46aea8 | 2018-10-29 10:08:01 -0700 | [diff] [blame] | 22 | import static javax.lang.model.element.Modifier.FINAL; |
| ronshapiro | ad1ed9f | 2018-11-19 07:32:53 -0800 | [diff] [blame] | 23 | import static javax.lang.model.element.Modifier.PROTECTED; |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 24 | import static javax.lang.model.element.Modifier.PUBLIC; |
| 25 | |
| ronshapiro | a46aea8 | 2018-10-29 10:08:01 -0700 | [diff] [blame] | 26 | import com.google.common.collect.ImmutableSet; |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 27 | import com.squareup.javapoet.MethodSpec; |
| 28 | import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor; |
| 29 | import dagger.internal.codegen.ModifiableBindingMethods.ModifiableBindingMethod; |
| 30 | import dagger.model.BindingKind; |
| ronshapiro | ac53c25 | 2018-11-26 08:04:55 -0800 | [diff] [blame] | 31 | import dagger.model.Scope; |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 32 | import java.util.Optional; |
| 33 | |
| 34 | /** |
| 35 | * A central repository of code expressions used to access modifiable bindings available to a |
| 36 | * component. A binding is modifiable if it can be modified across implementations of a |
| 37 | * subcomponent. This is only relevant for ahead-of-time subcomponents. |
| 38 | */ |
| 39 | final class ModifiableBindingExpressions { |
| 40 | private final Optional<ModifiableBindingExpressions> parent; |
| 41 | private final ComponentBindingExpressions bindingExpressions; |
| 42 | private final BindingGraph graph; |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 43 | private final ComponentImplementation componentImplementation; |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 44 | private final CompilerOptions compilerOptions; |
| ronshapiro | 8d759b2 | 2018-11-05 09:57:39 -0800 | [diff] [blame] | 45 | private final DaggerTypes types; |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 46 | |
| 47 | ModifiableBindingExpressions( |
| 48 | Optional<ModifiableBindingExpressions> parent, |
| 49 | ComponentBindingExpressions bindingExpressions, |
| 50 | BindingGraph graph, |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 51 | ComponentImplementation componentImplementation, |
| ronshapiro | 8d759b2 | 2018-11-05 09:57:39 -0800 | [diff] [blame] | 52 | CompilerOptions compilerOptions, |
| 53 | DaggerTypes types) { |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 54 | this.parent = parent; |
| 55 | this.bindingExpressions = bindingExpressions; |
| 56 | this.graph = graph; |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 57 | this.componentImplementation = componentImplementation; |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 58 | this.compilerOptions = compilerOptions; |
| ronshapiro | 8d759b2 | 2018-11-05 09:57:39 -0800 | [diff] [blame] | 59 | this.types = types; |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 60 | } |
| 61 | |
| 62 | /** |
| 63 | * Records the binding exposed by the given component method as modifiable, if it is, and returns |
| 64 | * the {@link ModifiableBindingType} associated with the binding. |
| 65 | */ |
| 66 | ModifiableBindingType registerComponentMethodIfModifiable( |
| 67 | ComponentMethodDescriptor componentMethod, MethodSpec method) { |
| 68 | BindingRequest request = bindingRequest(componentMethod.dependencyRequest().get()); |
| 69 | ModifiableBindingType modifiableBindingType = getModifiableBindingType(request); |
| 70 | if (modifiableBindingType.isModifiable()) { |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 71 | componentImplementation.registerModifiableBindingMethod( |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 72 | modifiableBindingType, |
| 73 | request, |
| 74 | method, |
| 75 | newModifiableBindingWillBeFinalized(modifiableBindingType, request)); |
| 76 | } |
| 77 | return modifiableBindingType; |
| 78 | } |
| 79 | |
| 80 | /** |
| 81 | * Returns the implementation of a modifiable binding method originally defined in a supertype |
| 82 | * implementation of this subcomponent. Returns {@link Optional#empty()} when the binding cannot |
| 83 | * or should not be modified by the current binding graph. |
| 84 | */ |
| ronshapiro | db0d66b | 2018-11-19 12:16:43 -0800 | [diff] [blame] | 85 | Optional<ModifiableBindingMethod> reimplementedModifiableBindingMethod( |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 86 | ModifiableBindingMethod modifiableBindingMethod) { |
| ronshapiro | db0d66b | 2018-11-19 12:16:43 -0800 | [diff] [blame] | 87 | checkState(componentImplementation.superclassImplementation().isPresent()); |
| 88 | if (modifiableBindingTypeChanged(modifiableBindingMethod) |
| 89 | || shouldModifyImplementation( |
| 90 | modifiableBindingMethod.type(), modifiableBindingMethod.request())) { |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 91 | MethodSpec baseMethod = modifiableBindingMethod.methodSpec(); |
| ronshapiro | a46aea8 | 2018-10-29 10:08:01 -0700 | [diff] [blame] | 92 | boolean markMethodFinal = |
| 93 | knownModifiableBindingWillBeFinalized(modifiableBindingMethod) |
| 94 | // no need to mark the method final if the component implementation will be final |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 95 | && componentImplementation.isAbstract(); |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 96 | return Optional.of( |
| 97 | ModifiableBindingMethod.implement( |
| 98 | modifiableBindingMethod, |
| 99 | MethodSpec.methodBuilder(baseMethod.name) |
| ronshapiro | ad1ed9f | 2018-11-19 07:32:53 -0800 | [diff] [blame] | 100 | .addModifiers(baseMethod.modifiers.contains(PUBLIC) ? PUBLIC : PROTECTED) |
| ronshapiro | a46aea8 | 2018-10-29 10:08:01 -0700 | [diff] [blame] | 101 | .addModifiers(markMethodFinal ? ImmutableSet.of(FINAL) : ImmutableSet.of()) |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 102 | .returns(baseMethod.returnType) |
| 103 | .addAnnotation(Override.class) |
| 104 | .addCode( |
| 105 | bindingExpressions |
| 106 | .getBindingExpression(modifiableBindingMethod.request()) |
| 107 | .getModifiableBindingMethodImplementation( |
| ronshapiro | 66dd69f | 2018-11-27 08:49:26 -0800 | [diff] [blame^] | 108 | modifiableBindingMethod, componentImplementation, types)) |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 109 | .build(), |
| ronshapiro | a46aea8 | 2018-10-29 10:08:01 -0700 | [diff] [blame] | 110 | markMethodFinal)); |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 111 | } |
| 112 | return Optional.empty(); |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * Returns true if a modifiable binding method that was registered in a superclass implementation |
| 117 | * of this subcomponent should be marked as "finalized" if it is being overridden by this |
| 118 | * subcomponent implementation. "Finalized" means we should not attempt to modify the binding in |
| 119 | * any subcomponent subclass. |
| 120 | */ |
| 121 | private boolean knownModifiableBindingWillBeFinalized( |
| 122 | ModifiableBindingMethod modifiableBindingMethod) { |
| 123 | ModifiableBindingType newModifiableBindingType = |
| 124 | getModifiableBindingType(modifiableBindingMethod.request()); |
| 125 | if (!newModifiableBindingType.isModifiable()) { |
| 126 | // If a modifiable binding has become non-modifiable it is final by definition. |
| 127 | return true; |
| 128 | } |
| 129 | return modifiableBindingWillBeFinalized( |
| 130 | newModifiableBindingType, |
| ronshapiro | db0d66b | 2018-11-19 12:16:43 -0800 | [diff] [blame] | 131 | shouldModifyImplementation(newModifiableBindingType, modifiableBindingMethod.request())); |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 132 | } |
| 133 | |
| 134 | /** |
| 135 | * Returns true if a newly discovered modifiable binding method, once it is defined in this |
| 136 | * subcomponent implementation, should be marked as "finalized", meaning we should not attempt to |
| 137 | * modify the binding in any subcomponent subclass. |
| 138 | */ |
| 139 | private boolean newModifiableBindingWillBeFinalized( |
| 140 | ModifiableBindingType modifiableBindingType, BindingRequest request) { |
| 141 | return modifiableBindingWillBeFinalized( |
| ronshapiro | db0d66b | 2018-11-19 12:16:43 -0800 | [diff] [blame] | 142 | modifiableBindingType, shouldModifyImplementation(modifiableBindingType, request)); |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 143 | } |
| 144 | |
| 145 | /** |
| 146 | * Returns true if we shouldn't attempt to further modify a modifiable binding once we complete |
| 147 | * the implementation for the current subcomponent. |
| 148 | */ |
| 149 | private boolean modifiableBindingWillBeFinalized( |
| 150 | ModifiableBindingType modifiableBindingType, boolean modifyingBinding) { |
| 151 | switch (modifiableBindingType) { |
| 152 | case MISSING: |
| 153 | case GENERATED_INSTANCE: |
| 154 | case OPTIONAL: |
| 155 | case INJECTION: |
| 156 | // Once we modify any of the above a single time, then they are finalized. |
| 157 | return modifyingBinding; |
| ronshapiro | ac53c25 | 2018-11-26 08:04:55 -0800 | [diff] [blame] | 158 | case PRODUCTION: |
| 159 | // For production bindings, we know that the binding will be finalized if the parent is a |
| 160 | // non-production component, but for @ProductionScope bindings we don't ever know because an |
| 161 | // ancestor non-production component can apply @ProductionScope. We therefore return false |
| 162 | // always. If we wanted, we could create a separate ModifiableBindingType for production |
| 163 | // scope to allow us to make this distinction. |
| 164 | return false; |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 165 | case MULTIBINDING: |
| dstrasburg | dd0e7b0 | 2018-10-04 08:24:41 -0700 | [diff] [blame] | 166 | case MODULE_INSTANCE: |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 167 | return false; |
| 168 | default: |
| 169 | throw new IllegalStateException( |
| 170 | String.format( |
| 171 | "Building binding expression for unsupported ModifiableBindingType [%s].", |
| 172 | modifiableBindingType)); |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | /** |
| 177 | * Creates a binding expression for a binding if it may be modified across implementations of a |
| 178 | * subcomponent. |
| 179 | */ |
| 180 | Optional<BindingExpression> maybeCreateModifiableBindingExpression(BindingRequest request) { |
| 181 | ModifiableBindingType type = getModifiableBindingType(request); |
| 182 | if (!type.isModifiable()) { |
| 183 | return Optional.empty(); |
| 184 | } |
| 185 | return Optional.of(createModifiableBindingExpression(type, request)); |
| 186 | } |
| 187 | |
| 188 | /** Creates a binding expression for a modifiable binding. */ |
| 189 | private BindingExpression createModifiableBindingExpression( |
| 190 | ModifiableBindingType type, BindingRequest request) { |
| 191 | ResolvedBindings resolvedBindings = graph.resolvedBindings(request); |
| 192 | Optional<ModifiableBindingMethod> matchingModifiableBindingMethod = |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 193 | componentImplementation.getModifiableBindingMethod(request); |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 194 | Optional<ComponentMethodDescriptor> matchingComponentMethod = |
| dpb | 115b10e | 2018-11-14 09:08:31 -0800 | [diff] [blame] | 195 | graph.componentDescriptor().firstMatchingComponentMethod(request); |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 196 | switch (type) { |
| 197 | case GENERATED_INSTANCE: |
| dstrasburg | e38705f | 2018-10-16 18:26:35 -0700 | [diff] [blame] | 198 | // If the subcomponent is abstract then we need to define an (un-implemented) |
| 199 | // GeneratedInstanceBindingExpression. |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 200 | if (componentImplementation.isAbstract()) { |
| dstrasburg | e38705f | 2018-10-16 18:26:35 -0700 | [diff] [blame] | 201 | return new GeneratedInstanceBindingExpression( |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 202 | componentImplementation, |
| dstrasburg | e38705f | 2018-10-16 18:26:35 -0700 | [diff] [blame] | 203 | resolvedBindings, |
| 204 | request, |
| 205 | matchingModifiableBindingMethod, |
| ronshapiro | 1ae4c00 | 2018-11-16 08:23:34 -0800 | [diff] [blame] | 206 | matchingComponentMethod, |
| 207 | types); |
| dstrasburg | e38705f | 2018-10-16 18:26:35 -0700 | [diff] [blame] | 208 | } |
| 209 | // Otherwise return a concrete implementation. |
| ronshapiro | a918531 | 2018-11-20 18:03:13 -0800 | [diff] [blame] | 210 | return bindingExpressions.createBindingExpression(resolvedBindings, request); |
| 211 | |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 212 | case MISSING: |
| dstrasburg | e38705f | 2018-10-16 18:26:35 -0700 | [diff] [blame] | 213 | // If we need an expression for a missing binding and the current implementation is |
| 214 | // abstract, then we need an (un-implemented) MissingBindingExpression. |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 215 | if (componentImplementation.isAbstract()) { |
| dstrasburg | e38705f | 2018-10-16 18:26:35 -0700 | [diff] [blame] | 216 | return new MissingBindingExpression( |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 217 | componentImplementation, |
| dstrasburg | e38705f | 2018-10-16 18:26:35 -0700 | [diff] [blame] | 218 | request, |
| 219 | matchingModifiableBindingMethod, |
| ronshapiro | 1ae4c00 | 2018-11-16 08:23:34 -0800 | [diff] [blame] | 220 | matchingComponentMethod, |
| 221 | types); |
| dstrasburg | e38705f | 2018-10-16 18:26:35 -0700 | [diff] [blame] | 222 | } |
| 223 | // Otherwise we assume that it is valid to have a missing binding as it is part of a |
| 224 | // dependency chain that has been passively pruned. |
| 225 | // TODO(b/117833324): Identify pruned bindings when generating the subcomponent |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 226 | // implementation in which the bindings are pruned. If we hold a reference to the binding |
| 227 | // graph used to generate a given implementation then we can compare a implementation's |
| 228 | // graph with its superclass implementation's graph to detect pruned dependency branches. |
| dstrasburg | e38705f | 2018-10-16 18:26:35 -0700 | [diff] [blame] | 229 | return new PrunedConcreteMethodBindingExpression(); |
| ronshapiro | a918531 | 2018-11-20 18:03:13 -0800 | [diff] [blame] | 230 | |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 231 | case OPTIONAL: |
| 232 | case MULTIBINDING: |
| 233 | case INJECTION: |
| dstrasburg | dd0e7b0 | 2018-10-04 08:24:41 -0700 | [diff] [blame] | 234 | case MODULE_INSTANCE: |
| ronshapiro | ac53c25 | 2018-11-26 08:04:55 -0800 | [diff] [blame] | 235 | case PRODUCTION: |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 236 | return bindingExpressions.wrapInMethod( |
| 237 | resolvedBindings, |
| 238 | request, |
| 239 | bindingExpressions.createBindingExpression(resolvedBindings, request)); |
| 240 | default: |
| 241 | throw new IllegalStateException( |
| 242 | String.format( |
| 243 | "Building binding expression for unsupported ModifiableBindingType [%s].", type)); |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | /** |
| 248 | * The reason why a binding may need to be modified across implementations of a subcomponent, if |
| 249 | * at all. |
| 250 | */ |
| ronshapiro | ab61b70 | 2018-11-21 12:23:33 -0800 | [diff] [blame] | 251 | ModifiableBindingType getModifiableBindingType(BindingRequest request) { |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 252 | if (!compilerOptions.aheadOfTimeSubcomponents()) { |
| 253 | return ModifiableBindingType.NONE; |
| 254 | } |
| 255 | |
| dstrasburg | e38705f | 2018-10-16 18:26:35 -0700 | [diff] [blame] | 256 | // When generating a component the binding is not considered modifiable. Bindings are modifiable |
| 257 | // only across subcomponent implementations. |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 258 | if (componentImplementation.componentDescriptor().kind().isTopLevel()) { |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 259 | return ModifiableBindingType.NONE; |
| 260 | } |
| 261 | |
| 262 | if (resolvedInThisComponent(request)) { |
| 263 | ResolvedBindings resolvedBindings = graph.resolvedBindings(request); |
| 264 | if (resolvedBindings.contributionBindings().isEmpty()) { |
| 265 | // TODO(ronshapiro): Confirm whether a resolved binding must have a single contribution |
| 266 | // binding. |
| 267 | return ModifiableBindingType.NONE; |
| 268 | } |
| 269 | |
| 270 | ContributionBinding binding = resolvedBindings.contributionBinding(); |
| 271 | if (binding.requiresGeneratedInstance()) { |
| 272 | return ModifiableBindingType.GENERATED_INSTANCE; |
| 273 | } |
| 274 | |
| ronshapiro | 28f06f2 | 2018-11-12 14:03:15 -0800 | [diff] [blame] | 275 | if (binding.kind().equals(BindingKind.DELEGATE) |
| 276 | && graph |
| 277 | .contributionBindings() |
| 278 | .get(getOnlyElement(binding.dependencies()).key()) |
| 279 | .isEmpty()) { |
| 280 | // There's not much to do for @Binds bindings if the dependency is missing - at best, if the |
| 281 | // dependency is a weaker scope/unscoped, we save only a few lines that implement the |
| 282 | // scoping. But it's also possible, if the dependency is the same or stronger scope, that |
| 283 | // no extra code is necessary, in which case we'd be overriding a method that just returns |
| 284 | // another. |
| 285 | return ModifiableBindingType.MISSING; |
| 286 | } |
| 287 | |
| ronshapiro | 3bd8632 | 2018-10-29 13:18:38 -0700 | [diff] [blame] | 288 | if (binding.kind().equals(BindingKind.OPTIONAL) && binding.dependencies().isEmpty()) { |
| 289 | // only empty optional bindings can be modified |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 290 | return ModifiableBindingType.OPTIONAL; |
| 291 | } |
| 292 | |
| ronshapiro | 8d759b2 | 2018-11-05 09:57:39 -0800 | [diff] [blame] | 293 | if (binding.isSyntheticMultibinding()) { |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 294 | return ModifiableBindingType.MULTIBINDING; |
| 295 | } |
| 296 | |
| 297 | if (binding.kind().equals(BindingKind.INJECTION)) { |
| 298 | return ModifiableBindingType.INJECTION; |
| 299 | } |
| dstrasburg | dd0e7b0 | 2018-10-04 08:24:41 -0700 | [diff] [blame] | 300 | |
| ronshapiro | ba2e380 | 2018-11-26 18:30:29 -0800 | [diff] [blame] | 301 | // TODO(b/117833324): Check whether we need to modify a module instance binding if we are |
| dstrasburg | dd0e7b0 | 2018-10-04 08:24:41 -0700 | [diff] [blame] | 302 | // correctly installing the new module instance. In other words, if there is a subcomponent |
| 303 | // builder should we consider a module instance binding modifiable? |
| 304 | if (binding.requiresModuleInstance()) { |
| 305 | return ModifiableBindingType.MODULE_INSTANCE; |
| 306 | } |
| ronshapiro | ac53c25 | 2018-11-26 08:04:55 -0800 | [diff] [blame] | 307 | |
| 308 | if ((binding.scope().map(Scope::isProductionScope).orElse(false) |
| 309 | && componentImplementation.isAbstract()) |
| 310 | || binding.bindingType().equals(BindingType.PRODUCTION)) { |
| 311 | return ModifiableBindingType.PRODUCTION; |
| 312 | } |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 313 | } else if (!resolvableBinding(request)) { |
| 314 | return ModifiableBindingType.MISSING; |
| 315 | } |
| 316 | |
| 317 | return ModifiableBindingType.NONE; |
| 318 | } |
| 319 | |
| 320 | /** |
| ronshapiro | db0d66b | 2018-11-19 12:16:43 -0800 | [diff] [blame] | 321 | * Returns true if the modifiable binding type of a {@code modifiableBindingMethod}'s request is |
| 322 | * different in this implementation from what it was in the super implementation. |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 323 | */ |
| ronshapiro | db0d66b | 2018-11-19 12:16:43 -0800 | [diff] [blame] | 324 | private boolean modifiableBindingTypeChanged(ModifiableBindingMethod modifiableBindingMethod) { |
| 325 | checkState(componentImplementation.superclassImplementation().isPresent()); |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 326 | ModifiableBindingType newModifiableBindingType = |
| 327 | getModifiableBindingType(modifiableBindingMethod.request()); |
| ronshapiro | db0d66b | 2018-11-19 12:16:43 -0800 | [diff] [blame] | 328 | return !newModifiableBindingType.equals(modifiableBindingMethod.type()); |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 329 | } |
| 330 | |
| 331 | /** |
| 332 | * Returns true if the current binding graph can, and should, modify a binding by overriding a |
| 333 | * modifiable binding method. |
| 334 | */ |
| ronshapiro | db0d66b | 2018-11-19 12:16:43 -0800 | [diff] [blame] | 335 | private boolean shouldModifyImplementation( |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 336 | ModifiableBindingType modifiableBindingType, BindingRequest request) { |
| ronshapiro | 8d759b2 | 2018-11-05 09:57:39 -0800 | [diff] [blame] | 337 | if (request.requestKind().isPresent()) { |
| 338 | switch (request.requestKind().get()) { |
| 339 | case FUTURE: |
| 340 | // Futures are always requested by a Producer.get() call, so if the binding is modifiable, |
| 341 | // the producer will be wrapped in a modifiable method and the future can refer to that |
| 342 | // method; even if the producer binding is modified, getModifiableProducer().get() will |
| 343 | // never need to be modified. Furthermore, because cancellation is treated by wrapped |
| 344 | // producers, and those producers point to the modifiable producer wrapper methods, we |
| 345 | // never need or want to change the access of these wrapped producers for entry |
| 346 | // methods |
| 347 | return false; |
| 348 | |
| 349 | case LAZY: |
| 350 | case PROVIDER_OF_LAZY: |
| 351 | // Lazy and ProviderOfLazy are always created from a Provider, and therefore this request |
| 352 | // never needs to be modifiable. It will refer (via DoubleCheck.lazy() or |
| 353 | // ProviderOfLazy.create()) to the modifiable method and not the framework instance. |
| 354 | return false; |
| 355 | |
| 356 | case MEMBERS_INJECTION: |
| 357 | case PRODUCED: |
| 358 | // MEMBERS_INJECTION has a completely different code path for binding expressions, and |
| 359 | // PRODUCED requests are only requestable in @Produces methods, which are hidden from |
| 360 | // generated components inside Producer factories |
| 361 | throw new AssertionError(request); |
| 362 | |
| 363 | case INSTANCE: |
| 364 | case PROVIDER: |
| 365 | case PRODUCER: |
| 366 | // These may be modifiable, so run through the regular logic. They're spelled out |
| 367 | // explicitly so that ErrorProne will detect if a new enum value is created and missing |
| 368 | // from this list. |
| 369 | break; |
| 370 | } |
| 371 | } |
| 372 | |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 373 | ResolvedBindings resolvedBindings = graph.resolvedBindings(request); |
| 374 | switch (modifiableBindingType) { |
| 375 | case GENERATED_INSTANCE: |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 376 | return !componentImplementation.isAbstract(); |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 377 | case MISSING: |
| ronshapiro | ba2e380 | 2018-11-26 18:30:29 -0800 | [diff] [blame] | 378 | // TODO(b/117833324): investigate beder@'s comment about having intermediate component |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 379 | // ancestors satisfy missing bindings of their children with their own missing binding |
| 380 | // methods so that we can minimize the cases where we need to reach into doubly-nested |
| dstrasburg | e38705f | 2018-10-16 18:26:35 -0700 | [diff] [blame] | 381 | // descendant component implementations. |
| 382 | |
| 383 | // Implement a missing binding if it is resolvable, or if we're generating a concrete |
| 384 | // subcomponent implementation. If a binding is still missing when the subcomponent |
| 385 | // implementation is concrete then it is assumed to be part of a dependency that would have |
| 386 | // been passively pruned when implementing the full component hierarchy. |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 387 | return resolvableBinding(request) || !componentImplementation.isAbstract(); |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 388 | case OPTIONAL: |
| 389 | // Only override optional binding methods if we have a non-empty binding. |
| 390 | return !resolvedBindings.contributionBinding().dependencies().isEmpty(); |
| 391 | case MULTIBINDING: |
| 392 | // Only modify a multibinding if there are new contributions. |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 393 | return !componentImplementation |
| ronshapiro | 8d759b2 | 2018-11-05 09:57:39 -0800 | [diff] [blame] | 394 | .superclassContributionsMade(request) |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 395 | .containsAll(resolvedBindings.contributionBinding().dependencies()); |
| 396 | case INJECTION: |
| 397 | return !resolvedBindings.contributionBinding().kind().equals(BindingKind.INJECTION); |
| dstrasburg | dd0e7b0 | 2018-10-04 08:24:41 -0700 | [diff] [blame] | 398 | case MODULE_INSTANCE: |
| 399 | // At the moment we have no way of detecting whether a new module instance is installed and |
| 400 | // the implementation has changed, so we implement the binding once in the base |
| 401 | // implementation of the subcomponent. It will be re-implemented when generating the |
| 402 | // component. |
| dpb | 159d81b | 2018-11-01 14:06:17 -0700 | [diff] [blame] | 403 | return !componentImplementation.superclassImplementation().isPresent() |
| 404 | || !componentImplementation.isAbstract(); |
| ronshapiro | ac53c25 | 2018-11-26 08:04:55 -0800 | [diff] [blame] | 405 | case PRODUCTION: |
| 406 | // TODO(b/117833324): Profile this to see if this check is slow |
| 407 | return !resolvedBindings |
| 408 | .owningComponent() |
| 409 | .equals(componentImplementation.componentDescriptor()); |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 410 | default: |
| 411 | throw new IllegalStateException( |
| 412 | String.format( |
| 413 | "Overriding modifiable binding method with unsupported ModifiableBindingType [%s].", |
| 414 | modifiableBindingType)); |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | /** |
| 419 | * Returns true if the binding can be resolved by the graph for this component or any parent |
| 420 | * component. |
| 421 | */ |
| 422 | private boolean resolvableBinding(BindingRequest request) { |
| 423 | for (ModifiableBindingExpressions expressions = this; |
| 424 | expressions != null; |
| 425 | expressions = expressions.parent.orElse(null)) { |
| 426 | if (expressions.resolvedInThisComponent(request)) { |
| 427 | return true; |
| 428 | } |
| 429 | } |
| 430 | return false; |
| 431 | } |
| 432 | |
| 433 | /** Returns true if the binding can be resolved by the graph for this component. */ |
| 434 | private boolean resolvedInThisComponent(BindingRequest request) { |
| 435 | ResolvedBindings resolvedBindings = graph.resolvedBindings(request); |
| 436 | return resolvedBindings != null && !resolvedBindings.ownedBindings().isEmpty(); |
| 437 | } |
| 438 | |
| 439 | /** |
| ronshapiro | ab61b70 | 2018-11-21 12:23:33 -0800 | [diff] [blame] | 440 | * Wraps a modifiable binding expression in a method that can be overridden in a subclass |
| 441 | * implementation. |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 442 | */ |
| ronshapiro | ab61b70 | 2018-11-21 12:23:33 -0800 | [diff] [blame] | 443 | BindingExpression wrapInModifiableMethodBindingExpression( |
| 444 | ContributionBinding binding, |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 445 | BindingRequest request, |
| ronshapiro | ab61b70 | 2018-11-21 12:23:33 -0800 | [diff] [blame] | 446 | BindingMethodImplementation methodImplementation) { |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 447 | ModifiableBindingType modifiableBindingType = getModifiableBindingType(request); |
| ronshapiro | ab61b70 | 2018-11-21 12:23:33 -0800 | [diff] [blame] | 448 | checkState(modifiableBindingType.isModifiable()); |
| 449 | return new ModifiableConcreteMethodBindingExpression( |
| 450 | binding, |
| 451 | request, |
| 452 | modifiableBindingType, |
| 453 | methodImplementation, |
| 454 | componentImplementation, |
| 455 | newModifiableBindingWillBeFinalized(modifiableBindingType, request), |
| 456 | types); |
| dstrasburg | f0f6dfb | 2018-09-27 12:00:42 -0700 | [diff] [blame] | 457 | } |
| 458 | } |