| /** |
| * Copyright (C) 2011 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 com.google.inject.grapher; |
| |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| import com.google.inject.Binding; |
| import com.google.inject.Injector; |
| import com.google.inject.Key; |
| import java.io.IOException; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Abstract injector grapher that builds the dependency graph but doesn't render it. |
| * |
| * @author bojand@google.com (Bojan Djordjevic) |
| */ |
| public abstract class AbstractInjectorGrapher implements InjectorGrapher { |
| private final RootKeySetCreator rootKeySetCreator; |
| private final AliasCreator aliasCreator; |
| private final NodeCreator nodeCreator; |
| private final EdgeCreator edgeCreator; |
| |
| /** Parameters used to override default settings of the grapher. */ |
| public static final class GrapherParameters { |
| private RootKeySetCreator rootKeySetCreator = new DefaultRootKeySetCreator(); |
| private AliasCreator aliasCreator = new ProviderAliasCreator(); |
| private NodeCreator nodeCreator = new DefaultNodeCreator(); |
| private EdgeCreator edgeCreator = new DefaultEdgeCreator(); |
| |
| public RootKeySetCreator getRootKeySetCreator() { |
| return rootKeySetCreator; |
| } |
| |
| public GrapherParameters setRootKeySetCreator(RootKeySetCreator rootKeySetCreator) { |
| this.rootKeySetCreator = rootKeySetCreator; |
| return this; |
| } |
| |
| public AliasCreator getAliasCreator() { |
| return aliasCreator; |
| } |
| |
| public GrapherParameters setAliasCreator(AliasCreator aliasCreator) { |
| this.aliasCreator = aliasCreator; |
| return this; |
| } |
| |
| public NodeCreator getNodeCreator() { |
| return nodeCreator; |
| } |
| |
| public GrapherParameters setNodeCreator(NodeCreator nodeCreator) { |
| this.nodeCreator = nodeCreator; |
| return this; |
| } |
| |
| public EdgeCreator getEdgeCreator() { |
| return edgeCreator; |
| } |
| |
| public GrapherParameters setEdgeCreator(EdgeCreator edgeCreator) { |
| this.edgeCreator = edgeCreator; |
| return this; |
| } |
| } |
| |
| public AbstractInjectorGrapher() { |
| this(new GrapherParameters()); |
| } |
| |
| public AbstractInjectorGrapher(GrapherParameters options) { |
| this.rootKeySetCreator = options.getRootKeySetCreator(); |
| this.aliasCreator = options.getAliasCreator(); |
| this.nodeCreator = options.getNodeCreator(); |
| this.edgeCreator = options.getEdgeCreator(); |
| } |
| |
| @Override public final void graph(Injector injector) throws IOException { |
| graph(injector, rootKeySetCreator.getRootKeys(injector)); |
| } |
| |
| @Override public final void graph(Injector injector, Set<Key<?>> root) throws IOException { |
| reset(); |
| |
| Iterable<Binding<?>> bindings = getBindings(injector, root); |
| Map<NodeId, NodeId> aliases = resolveAliases(aliasCreator.createAliases(bindings)); |
| createNodes(nodeCreator.getNodes(bindings), aliases); |
| createEdges(edgeCreator.getEdges(bindings), aliases); |
| postProcess(); |
| } |
| |
| /** Resets the state of the grapher before rendering a new graph. */ |
| protected abstract void reset() throws IOException; |
| |
| /** Adds a new interface node to the graph. */ |
| protected abstract void newInterfaceNode(InterfaceNode node) throws IOException; |
| |
| /** Adds a new implementation node to the graph. */ |
| protected abstract void newImplementationNode(ImplementationNode node) throws IOException; |
| |
| /** Adds a new instance node to the graph. */ |
| protected abstract void newInstanceNode(InstanceNode node) throws IOException; |
| |
| /** Adds a new dependency edge to the graph. */ |
| protected abstract void newDependencyEdge(DependencyEdge edge) throws IOException; |
| |
| /** Adds a new binding edge to the graph. */ |
| protected abstract void newBindingEdge(BindingEdge edge) throws IOException; |
| |
| /** Performs any post processing required after all nodes and edges have been added. */ |
| protected abstract void postProcess() throws IOException; |
| |
| private void createNodes(Iterable<Node> nodes, Map<NodeId, NodeId> aliases) throws IOException { |
| for (Node node : nodes) { |
| NodeId originalId = node.getId(); |
| NodeId resolvedId = resolveAlias(aliases, originalId); |
| node = node.copy(resolvedId); |
| |
| // Only render nodes that aren't aliased to some other node. |
| if (resolvedId.equals(originalId)) { |
| if (node instanceof InterfaceNode) { |
| newInterfaceNode((InterfaceNode) node); |
| } else if (node instanceof ImplementationNode) { |
| newImplementationNode((ImplementationNode) node); |
| } else { |
| newInstanceNode((InstanceNode) node); |
| } |
| } |
| } |
| } |
| |
| private void createEdges(Iterable<Edge> edges, Map<NodeId, NodeId> aliases) throws IOException { |
| for (Edge edge : edges) { |
| edge = edge.copy(resolveAlias(aliases, edge.getFromId()), |
| resolveAlias(aliases, edge.getToId())); |
| if (!edge.getFromId().equals(edge.getToId())) { |
| if (edge instanceof BindingEdge) { |
| newBindingEdge((BindingEdge) edge); |
| } else { |
| newDependencyEdge((DependencyEdge) edge); |
| } |
| } |
| } |
| } |
| |
| private NodeId resolveAlias(Map<NodeId, NodeId> aliases, NodeId nodeId) { |
| return aliases.containsKey(nodeId) ? aliases.get(nodeId) : nodeId; |
| } |
| |
| /** |
| * Transitively resolves aliases. Given aliases (X to Y) and (Y to Z), it will return mappings |
| * (X to Z) and (Y to Z). |
| */ |
| private Map<NodeId, NodeId> resolveAliases(Iterable<Alias> aliases) { |
| Map<NodeId, NodeId> resolved = Maps.newHashMap(); |
| Map<NodeId, Set<NodeId>> inverse = Maps.newHashMap(); |
| |
| for (Alias alias : aliases) { |
| NodeId from = alias.getFromId(); |
| NodeId to = alias.getToId(); |
| if (resolved.containsKey(to)) { |
| to = resolved.get(to); |
| } |
| resolved.put(from, to); |
| if (inverse.get(to) == null) { |
| inverse.put(to, Sets.<NodeId>newHashSet()); |
| } |
| inverse.get(to).add(from); |
| |
| Set<NodeId> prev = inverse.get(from); |
| if (prev != null) { |
| for (NodeId id : prev) { |
| resolved.remove(id); |
| inverse.get(from).remove(id); |
| resolved.put(id, to); |
| inverse.get(to).add(id); |
| } |
| } |
| } |
| |
| return resolved; |
| } |
| |
| /** Returns the bindings for the root keys and their transitive dependencies. */ |
| private Iterable<Binding<?>> getBindings(Injector injector, Set<Key<?>> root) { |
| Set<Key<?>> keys = Sets.newHashSet(root); |
| Set<Key<?>> visitedKeys = Sets.newHashSet(); |
| List<Binding<?>> bindings = Lists.newArrayList(); |
| TransitiveDependencyVisitor keyVisitor = new TransitiveDependencyVisitor(); |
| |
| while (!keys.isEmpty()) { |
| Iterator<Key<?>> iterator = keys.iterator(); |
| Key<?> key = iterator.next(); |
| iterator.remove(); |
| |
| if (!visitedKeys.contains(key)) { |
| Binding<?> binding = injector.getBinding(key); |
| bindings.add(binding); |
| visitedKeys.add(key); |
| keys.addAll(binding.acceptTargetVisitor(keyVisitor)); |
| } |
| } |
| return bindings; |
| } |
| } |