blob: 405d6b25ed98dc53fc33c05dac1964021e6f9249 [file] [log] [blame]
/*
* Copyright (C) 2024 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.binding;
import static com.google.common.base.Preconditions.checkState;
import static dagger.internal.codegen.extension.DaggerStreams.instancesOf;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.graph.EndpointPair;
import com.google.common.graph.MutableNetwork;
import com.google.common.graph.Network;
import com.google.common.graph.NetworkBuilder;
import dagger.internal.codegen.base.TarjanSCCs;
import dagger.internal.codegen.model.BindingGraph.Edge;
import dagger.internal.codegen.model.BindingGraph.Node;
import java.util.Map;
/** Transformations on the binding graph network. */
final class BindingGraphTransformations {
/** Returns a network where {@link BindingType} is present for all binding nodes. */
static MutableNetwork<Node, Edge> withFixedBindingTypes(MutableNetwork<Node, Edge> network) {
ImmutableSet<BindingNode> bindingsToFix = bindingsWithMissingBindingTypes(network);
if (bindingsToFix.isEmpty()) {
return network;
}
MutableNetwork<Node, Edge> fixedNetwork = withFixedBindingTypes(network, bindingsToFix);
// Check that all bindings now have a BindingType in the fixed network.
checkState(bindingsWithMissingBindingTypes(fixedNetwork).isEmpty());
return fixedNetwork;
}
private static MutableNetwork<Node, Edge> withFixedBindingTypes(
Network<Node, Edge> network, ImmutableSet<BindingNode> bindingsToFix) {
// Topologically sort the bindings so that we're guaranteed all dependencies of a binding are
// fixed before the bindings itself is fixed.
ImmutableList<ImmutableSet<BindingNode>> topologicallySortedBindingsToFix =
TarjanSCCs.compute(
bindingsToFix,
binding ->
network.successors(binding).stream()
.flatMap(instancesOf(BindingNode.class))
// Filter because we only care about direct dependencies on bindings that need
// to be fixed. There might be other cycles through nodes that already have a
// type, but those don't matter because it won't affect how we will fix the
// types for these bindings.
.filter(bindingsToFix::contains)
.collect(toImmutableSet()));
Map<BindingNode, BindingNode> replacements =
Maps.newHashMapWithExpectedSize(bindingsToFix.size());
for (ImmutableSet<BindingNode> connectedBindings : topologicallySortedBindingsToFix) {
BindingType successorBindingType =
connectedBindings.stream()
.flatMap(binding -> network.successors(binding).stream())
.flatMap(instancesOf(BindingNode.class))
.filter(binding -> !connectedBindings.contains(binding))
.map(binding -> replacements.getOrDefault(binding, binding))
.anyMatch(BindingNode::isProduction)
? BindingType.PRODUCTION
: BindingType.PROVISION;
for (BindingNode bindingNode : connectedBindings) {
replacements.put(bindingNode, bindingNode.withBindingType(successorBindingType));
}
}
return withReplacedBindings(network, ImmutableMap.copyOf(replacements));
}
private static ImmutableSet<BindingNode> bindingsWithMissingBindingTypes(
Network<Node, Edge> network) {
return network.nodes().stream()
.flatMap(instancesOf(BindingNode.class))
.filter(binding -> binding.delegate().optionalBindingType().isEmpty())
.collect(toImmutableSet());
}
// Note: This method creates an entirely new network rather than replacing individual nodes and
// edges in the original network. We can reconsider this choice, e.g. if it turns out to be
// too inefficient, but my initial thought is that this approach is a bit nicer because it
// maintains the original node and edge iteration order, which could be nice for debugging.
private static MutableNetwork<Node, Edge> withReplacedBindings(
Network<Node, Edge> network, ImmutableMap<? extends Node, ? extends Node> replacementNodes) {
MutableNetwork<Node, Edge> newNetwork = NetworkBuilder.from(network).build();
for (Node node : network.nodes()) {
newNetwork.addNode(replacementNodes.containsKey(node) ? replacementNodes.get(node) : node);
}
for (Edge edge : network.edges()) {
EndpointPair<Node> incidentNodes = network.incidentNodes(edge);
Node source = incidentNodes.source();
Node target = incidentNodes.target();
newNetwork.addEdge(
replacementNodes.containsKey(source) ? replacementNodes.get(source) : source,
replacementNodes.containsKey(target) ? replacementNodes.get(target) : target,
edge);
}
return newNetwork;
}
private BindingGraphTransformations() {}
}