1 /**
2  * Copyright (C) 2011 Google Inc.
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 com.google.inject.grapher;
18 
19 import com.google.common.collect.Lists;
20 import com.google.common.collect.Maps;
21 import com.google.common.collect.Sets;
22 import com.google.inject.Binding;
23 import com.google.inject.Injector;
24 import com.google.inject.Key;
25 import java.io.IOException;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 
31 /**
32  * Abstract injector grapher that builds the dependency graph but doesn't render it.
33  *
34  * @author bojand@google.com (Bojan Djordjevic)
35  * @since 4.0
36  */
37 public abstract class AbstractInjectorGrapher implements InjectorGrapher {
38   private final RootKeySetCreator rootKeySetCreator;
39   private final AliasCreator aliasCreator;
40   private final NodeCreator nodeCreator;
41   private final EdgeCreator edgeCreator;
42 
43   /**
44    * Parameters used to override default settings of the grapher.
45    * @since 4.0
46    */
47   public static final class GrapherParameters {
48     private RootKeySetCreator rootKeySetCreator = new DefaultRootKeySetCreator();
49     private AliasCreator aliasCreator = new ProviderAliasCreator();
50     private NodeCreator nodeCreator = new DefaultNodeCreator();
51     private EdgeCreator edgeCreator = new DefaultEdgeCreator();
52 
getRootKeySetCreator()53     public RootKeySetCreator getRootKeySetCreator() {
54       return rootKeySetCreator;
55     }
56 
setRootKeySetCreator(RootKeySetCreator rootKeySetCreator)57     public GrapherParameters setRootKeySetCreator(RootKeySetCreator rootKeySetCreator) {
58       this.rootKeySetCreator = rootKeySetCreator;
59       return this;
60     }
61 
getAliasCreator()62     public AliasCreator getAliasCreator() {
63       return aliasCreator;
64     }
65 
setAliasCreator(AliasCreator aliasCreator)66     public GrapherParameters setAliasCreator(AliasCreator aliasCreator) {
67       this.aliasCreator = aliasCreator;
68       return this;
69     }
70 
getNodeCreator()71     public NodeCreator getNodeCreator() {
72       return nodeCreator;
73     }
74 
setNodeCreator(NodeCreator nodeCreator)75     public GrapherParameters setNodeCreator(NodeCreator nodeCreator) {
76       this.nodeCreator = nodeCreator;
77       return this;
78     }
79 
getEdgeCreator()80     public EdgeCreator getEdgeCreator() {
81       return edgeCreator;
82     }
83 
setEdgeCreator(EdgeCreator edgeCreator)84     public GrapherParameters setEdgeCreator(EdgeCreator edgeCreator) {
85       this.edgeCreator = edgeCreator;
86       return this;
87     }
88   }
89 
AbstractInjectorGrapher()90   public AbstractInjectorGrapher() {
91     this(new GrapherParameters());
92   }
93 
AbstractInjectorGrapher(GrapherParameters options)94   public AbstractInjectorGrapher(GrapherParameters options) {
95     this.rootKeySetCreator = options.getRootKeySetCreator();
96     this.aliasCreator = options.getAliasCreator();
97     this.nodeCreator = options.getNodeCreator();
98     this.edgeCreator = options.getEdgeCreator();
99   }
100 
graph(Injector injector)101   @Override public final void graph(Injector injector) throws IOException {
102     graph(injector, rootKeySetCreator.getRootKeys(injector));
103   }
104 
graph(Injector injector, Set<Key<?>> root)105   @Override public final void graph(Injector injector, Set<Key<?>> root) throws IOException {
106     reset();
107 
108     Iterable<Binding<?>> bindings = getBindings(injector, root);
109     Map<NodeId, NodeId> aliases = resolveAliases(aliasCreator.createAliases(bindings));
110     createNodes(nodeCreator.getNodes(bindings), aliases);
111     createEdges(edgeCreator.getEdges(bindings), aliases);
112     postProcess();
113   }
114 
115   /** Resets the state of the grapher before rendering a new graph. */
reset()116   protected abstract void reset() throws IOException;
117 
118   /** Adds a new interface node to the graph. */
newInterfaceNode(InterfaceNode node)119   protected abstract void newInterfaceNode(InterfaceNode node) throws IOException;
120 
121   /** Adds a new implementation node to the graph. */
newImplementationNode(ImplementationNode node)122   protected abstract void newImplementationNode(ImplementationNode node) throws IOException;
123 
124   /** Adds a new instance node to the graph. */
newInstanceNode(InstanceNode node)125   protected abstract void newInstanceNode(InstanceNode node) throws IOException;
126 
127   /** Adds a new dependency edge to the graph. */
newDependencyEdge(DependencyEdge edge)128   protected abstract void newDependencyEdge(DependencyEdge edge) throws IOException;
129 
130   /** Adds a new binding edge to the graph. */
newBindingEdge(BindingEdge edge)131   protected abstract void newBindingEdge(BindingEdge edge) throws IOException;
132 
133   /** Performs any post processing required after all nodes and edges have been added. */
postProcess()134   protected abstract void postProcess() throws IOException;
135 
createNodes(Iterable<Node> nodes, Map<NodeId, NodeId> aliases)136   private void createNodes(Iterable<Node> nodes, Map<NodeId, NodeId> aliases) throws IOException {
137     for (Node node : nodes) {
138       NodeId originalId = node.getId();
139       NodeId resolvedId = resolveAlias(aliases, originalId);
140       node = node.copy(resolvedId);
141 
142       // Only render nodes that aren't aliased to some other node.
143       if (resolvedId.equals(originalId)) {
144         if (node instanceof InterfaceNode) {
145           newInterfaceNode((InterfaceNode) node);
146         } else if (node instanceof ImplementationNode) {
147           newImplementationNode((ImplementationNode) node);
148         } else {
149           newInstanceNode((InstanceNode) node);
150         }
151       }
152     }
153   }
154 
createEdges(Iterable<Edge> edges, Map<NodeId, NodeId> aliases)155   private void createEdges(Iterable<Edge> edges, Map<NodeId, NodeId> aliases) throws IOException {
156     for (Edge edge : edges) {
157       edge = edge.copy(resolveAlias(aliases, edge.getFromId()),
158           resolveAlias(aliases, edge.getToId()));
159       if (!edge.getFromId().equals(edge.getToId())) {
160         if (edge instanceof BindingEdge) {
161           newBindingEdge((BindingEdge) edge);
162         } else {
163           newDependencyEdge((DependencyEdge) edge);
164         }
165       }
166     }
167   }
168 
resolveAlias(Map<NodeId, NodeId> aliases, NodeId nodeId)169   private NodeId resolveAlias(Map<NodeId, NodeId> aliases, NodeId nodeId) {
170     return aliases.containsKey(nodeId) ? aliases.get(nodeId) : nodeId;
171   }
172 
173   /**
174    * Transitively resolves aliases. Given aliases (X to Y) and (Y to Z), it will return mappings
175    * (X to Z) and (Y to Z).
176    */
resolveAliases(Iterable<Alias> aliases)177   private Map<NodeId, NodeId> resolveAliases(Iterable<Alias> aliases) {
178     Map<NodeId, NodeId> resolved = Maps.newHashMap();
179     Map<NodeId, Set<NodeId>> inverse = Maps.newHashMap();
180 
181     for (Alias alias : aliases) {
182       NodeId from = alias.getFromId();
183       NodeId to = alias.getToId();
184       if (resolved.containsKey(to)) {
185         to = resolved.get(to);
186       }
187       resolved.put(from, to);
188       if (inverse.get(to) == null) {
189         inverse.put(to, Sets.<NodeId>newHashSet());
190       }
191       inverse.get(to).add(from);
192 
193       Set<NodeId> prev = inverse.get(from);
194       if (prev != null) {
195         for (NodeId id : prev) {
196           resolved.remove(id);
197           inverse.get(from).remove(id);
198           resolved.put(id, to);
199           inverse.get(to).add(id);
200         }
201       }
202     }
203 
204     return resolved;
205   }
206 
207   /** Returns the bindings for the root keys and their transitive dependencies. */
getBindings(Injector injector, Set<Key<?>> root)208   private Iterable<Binding<?>> getBindings(Injector injector, Set<Key<?>> root) {
209     Set<Key<?>> keys = Sets.newHashSet(root);
210     Set<Key<?>> visitedKeys = Sets.newHashSet();
211     List<Binding<?>> bindings = Lists.newArrayList();
212     TransitiveDependencyVisitor keyVisitor = new TransitiveDependencyVisitor();
213 
214     while (!keys.isEmpty()) {
215       Iterator<Key<?>> iterator = keys.iterator();
216       Key<?> key = iterator.next();
217       iterator.remove();
218 
219       if (!visitedKeys.contains(key)) {
220         Binding<?> binding = injector.getBinding(key);
221         bindings.add(binding);
222         visitedKeys.add(key);
223         keys.addAll(binding.acceptTargetVisitor(keyVisitor));
224       }
225     }
226     return bindings;
227   }
228 }
229