Refactoring of guice-grapher 
- makes it possible to inject non-scoped graphers
- fixes http://code.google.com/p/google-guice/issues/detail?id=489 (in
the sense that the exposed node is graphed, it still doesn't follow
private bindings)
- simplifies implementation classes:
- aliasing, node creating and edge creating are done as three
independent steps
- implementation classes will get aliased nodes so they don't need to
worry about aliasing
- edges are always created after the nodes they link
- removes boilerplate code to build node and edge factories


Revision created by MOE tool push_codebase.
MOE_MIGRATION=2701


git-svn-id: https://google-guice.googlecode.com/svn/trunk@1575 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/extensions/grapher/src/com/google/inject/grapher/AbstractInjectorGrapher.java b/extensions/grapher/src/com/google/inject/grapher/AbstractInjectorGrapher.java
new file mode 100644
index 0000000..da90ba3
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/AbstractInjectorGrapher.java
@@ -0,0 +1,224 @@
+/**
+ * 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;
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/Alias.java b/extensions/grapher/src/com/google/inject/grapher/Alias.java
new file mode 100644
index 0000000..eb68736
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/Alias.java
@@ -0,0 +1,41 @@
+/**
+ * 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;
+
+/**
+ * Alias between two nodes. Causes the 'from' node to be aliased with the 'to' node, which means
+ * that the 'from' node is not rendered and all edges going to it instead go to the 'to' node.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+public final class Alias {
+  private final NodeId fromId;
+  private final NodeId toId;
+
+  public Alias(NodeId fromId, NodeId toId) {
+    this.fromId = fromId;
+    this.toId = toId;
+  }
+
+  public NodeId getFromId() {
+    return fromId;
+  }
+
+  public NodeId getToId() {
+    return toId;
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/AliasCreator.java b/extensions/grapher/src/com/google/inject/grapher/AliasCreator.java
new file mode 100644
index 0000000..db9751e
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/AliasCreator.java
@@ -0,0 +1,37 @@
+/**
+ * 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.inject.Binding;
+
+/**
+ * Creator of node aliases. Used by dependency graphers to merge nodes in the internal Guice graph
+ * into a single node on the rendered graph.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+public interface AliasCreator {
+  /**
+   * Returns aliases for the given dependency graph. The aliases do not need to be transitively
+   * resolved, i.e. it is valid to return an alias (X to Y) and an alias (Y to Z). It is the
+   * responsibility of the caller to resolve this to (X to Z) and (Y to Z).
+   *
+   * @param bindings bindings that make up the dependency graph
+   * @return aliases that should be applied on the graph
+   */
+  Iterable<Alias> createAliases(Iterable<Binding<?>> bindings);
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/BindingEdge.java b/extensions/grapher/src/com/google/inject/grapher/BindingEdge.java
index 3ec9e79..9984f19 100644
--- a/extensions/grapher/src/com/google/inject/grapher/BindingEdge.java
+++ b/extensions/grapher/src/com/google/inject/grapher/BindingEdge.java
@@ -16,19 +16,18 @@
 
 package com.google.inject.grapher;
 
+import com.google.common.base.Objects;
+
 /**
- * Interface for an edge that connects an interface to the type or instance
- * that is bound to implement it.
+ * Edge that connects an interface to the type or instance that is bound to implement it.
  *
  * @author phopkins@gmail.com (Pete Hopkins)
- *
- * @param <K> The type for node IDs.
  */
-public interface BindingEdge<K> {
+public class BindingEdge extends Edge {
   /**
    * Classification for what kind of binding this edge represents.
    */
-  enum Type {
+  public enum Type {
     /** Binding is to an instance or class of the binding's same type. */
     NORMAL,
     /** Binding is to an instance or class that provides the binding's type. */
@@ -37,23 +36,34 @@
     CONVERTED_CONSTANT
   }
 
-  void setType(Type type);
+  private final Type type;
 
-  /**
-   * Factory interface for {@link BindingEdge}s. Renderer implementations will
-   * need to provide an implementation for this.
-   *
-   * @param <K> The type for node IDs.
-   * @param <T> The {@link BindingEdge} sub-type that this factory provides.
-   */
-  interface Factory<K, T extends BindingEdge<K>> {
-    /**
-     * Creates a new {@link BindingEdge} instance and adds it to the graph.
-     *
-     * @param fromId Node ID for the interface node.
-     * @param toId Node ID for the implementation (class or instance) node.
-     * @return The newly created and added {@link BindingEdge}.
-     */
-    T newBindingEdge(K fromId, K toId);
+  public BindingEdge(NodeId fromId, NodeId toId, Type type) {
+    super(fromId, toId);
+    this.type = type;
+  }
+
+  public Type getType() {
+    return type;
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (!(obj instanceof BindingEdge)) {
+      return false;
+    }
+    BindingEdge other = (BindingEdge) obj;
+    return super.equals(other) && Objects.equal(type, other.type);
+  }
+
+  @Override public int hashCode() {
+    return 31 * super.hashCode() + Objects.hashCode(type);
+  }
+
+  @Override public String toString() {
+    return "BindingEdge{fromId=" + getFromId() + " toId=" + getToId() + " type=" + type + "}";
+  }
+
+  @Override public Edge copy(NodeId fromId, NodeId toId) {
+    return new BindingEdge(fromId, toId, type);
   }
 }
diff --git a/extensions/grapher/src/com/google/inject/grapher/DefaultEdgeCreator.java b/extensions/grapher/src/com/google/inject/grapher/DefaultEdgeCreator.java
new file mode 100644
index 0000000..5ba402d
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/DefaultEdgeCreator.java
@@ -0,0 +1,154 @@
+/**
+ * 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.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.inject.Binding;
+import com.google.inject.spi.ConstructorBinding;
+import com.google.inject.spi.ConvertedConstantBinding;
+import com.google.inject.spi.DefaultBindingTargetVisitor;
+import com.google.inject.spi.Dependency;
+import com.google.inject.spi.HasDependencies;
+import com.google.inject.spi.InstanceBinding;
+import com.google.inject.spi.LinkedKeyBinding;
+import com.google.inject.spi.ProviderBinding;
+import com.google.inject.spi.ProviderInstanceBinding;
+import com.google.inject.spi.ProviderKeyBinding;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Default edge creator.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+final class DefaultEdgeCreator implements EdgeCreator {
+
+  @Override public Iterable<Edge> getEdges(Iterable<Binding<?>> bindings) {
+    List<Edge> edges = Lists.newArrayList();
+    EdgeVisitor visitor = new EdgeVisitor();
+    for (Binding<?> binding : bindings) {
+      edges.addAll(binding.acceptTargetVisitor(visitor));
+    }
+    return edges;
+  }
+
+  /**
+   * {@link BindingTargetVisitor} that adds edges to the graph based on the visited {@link Binding}.
+   */
+  private static final class EdgeVisitor
+      extends DefaultBindingTargetVisitor<Object, Collection<Edge>> {
+
+    /**
+     * Returns a dependency edge for each {@link Dependency} in the binding. These will be from the
+     * given node ID to the {@link Dependency}'s {@link Key}.
+     *
+     * @param nodeId ID of the node that should be the tail of the dependency
+     *     edges
+     * @param binding {@link Binding} for the dependencies
+     */
+    private <T extends Binding<?> & HasDependencies> Collection<Edge> newDependencyEdges(
+        NodeId nodeId, T binding) {
+      ImmutableList.Builder<Edge> builder = ImmutableList.builder();
+      for (Dependency<?> dependency : binding.getDependencies()) {
+        NodeId to = NodeId.newTypeId(dependency.getKey());
+        builder.add(new DependencyEdge(nodeId, to, dependency.getInjectionPoint()));
+      }
+      return builder.build();
+    }
+
+    /**
+     * Visitor for {@link ConstructorBinding}s. These are for classes that Guice will instantiate to
+     * satisfy injection requests.
+     */
+    @Override public Collection<Edge> visit(ConstructorBinding<?> binding) {
+      return newDependencyEdges(NodeId.newTypeId(binding.getKey()), binding);
+    }
+
+    /**
+     * Visitor for {@link ConvertedConstantBinding}. The {@link Binding}'s {@link Key} will be of an
+     * annotated primitive type, and the value of {@link ConvertedConstantBinding#getSourceKey()}
+     * will be of a {@link String} with the same annotation.
+     */
+    @Override public Collection<Edge> visit(ConvertedConstantBinding<?> binding) {
+      return ImmutableList.<Edge>of(new BindingEdge(NodeId.newTypeId(binding.getKey()),
+          NodeId.newTypeId(binding.getSourceKey()),
+          BindingEdge.Type.CONVERTED_CONSTANT));
+    }
+
+    /**
+     * Visitor for {@link InstanceBinding}. We then render any dependency edgess that the instance
+     * may have, which come either from {@link InjectionPoint}s (method and field) on the instance,
+     * or on {@link Dependency}s the instance declares through the {@link HasDependencies}
+     * interface.
+     */
+    @Override public Collection<Edge> visit(InstanceBinding<?> binding) {
+      return new ImmutableList.Builder<Edge>()
+          .add(new BindingEdge(NodeId.newTypeId(binding.getKey()),
+              NodeId.newInstanceId(binding.getKey()),
+              BindingEdge.Type.NORMAL))
+          .addAll(newDependencyEdges(NodeId.newInstanceId(binding.getKey()), binding))
+          .build();
+    }
+
+    /**
+     * Visitor for {@link LinkedKeyBinding}. This is the standard {@link Binding} you get from
+     * binding an interface class to an implementation class. We  draw a {@link BindingEdge} from
+     * the interface node to the node of the implementing class.
+     */
+    @Override public Collection<Edge> visit(LinkedKeyBinding<?> binding) {
+      return ImmutableList.<Edge>of(new BindingEdge(NodeId.newTypeId(binding.getKey()),
+          NodeId.newTypeId(binding.getLinkedKey()),
+          BindingEdge.Type.NORMAL));
+    }
+
+    /**
+     * Visitor for {@link ProviderBinding}. These {@link Binding}s arise from an
+     * {@link InjectionPoint} for the {@link Provider} interface.
+     */
+    @Override public Collection<Edge> visit(ProviderBinding<?> binding) {
+      return ImmutableList.<Edge>of(new BindingEdge(NodeId.newTypeId(binding.getKey()),
+          NodeId.newTypeId(binding.getProvidedKey()), BindingEdge.Type.PROVIDER));
+    }
+
+    /**
+     * Same as {@link #visit(InstanceBinding)}, but the binding edge is
+     * {@link BindingEdge.Type#PROVIDER}.
+     */
+    @Override public Collection<Edge> visit(ProviderInstanceBinding<?> binding) {
+      return new ImmutableList.Builder<Edge>()
+          .add(new BindingEdge(NodeId.newTypeId(binding.getKey()),
+              NodeId.newInstanceId(binding.getKey()), BindingEdge.Type.PROVIDER))
+          .addAll(newDependencyEdges(NodeId.newInstanceId(binding.getKey()), binding))
+          .build();
+    }
+
+    /**
+     * Same as {@link #visit(LinkedKeyBinding)}, but the binding edge is
+     * {@link BindingEdge.Type#PROVIDER}.
+     */
+    @Override public Collection<Edge> visit(ProviderKeyBinding<?> binding) {
+      return ImmutableList.<Edge>of(new BindingEdge(NodeId.newTypeId(binding.getKey()),
+          NodeId.newTypeId(binding.getProviderKey()), BindingEdge.Type.PROVIDER));
+    }
+
+    @Override public Collection<Edge> visitOther(Binding<?> binding) {
+      return ImmutableList.of();
+    }
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/DefaultNodeCreator.java b/extensions/grapher/src/com/google/inject/grapher/DefaultNodeCreator.java
new file mode 100644
index 0000000..557e850
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/DefaultNodeCreator.java
@@ -0,0 +1,130 @@
+/**
+ * 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.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.inject.Binding;
+import com.google.inject.spi.ConstructorBinding;
+import com.google.inject.spi.DefaultBindingTargetVisitor;
+import com.google.inject.spi.Dependency;
+import com.google.inject.spi.HasDependencies;
+import com.google.inject.spi.InjectionPoint;
+import com.google.inject.spi.InstanceBinding;
+import com.google.inject.spi.ProviderInstanceBinding;
+import java.lang.reflect.Member;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Default node creator.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+final class DefaultNodeCreator implements NodeCreator {
+  @Override public Iterable<Node> getNodes(Iterable<Binding<?>> bindings) {
+    List<Node> nodes = Lists.newArrayList();
+    NodeVisitor visitor = new NodeVisitor();
+    for (Binding<?> binding : bindings) {
+      nodes.addAll(binding.acceptTargetVisitor(visitor));
+    }
+    return nodes;
+  }
+
+  /**
+   * {@link BindingTargetVisitor} that adds nodes to the graph based on the visited {@link Binding}.
+   */
+  private static final class NodeVisitor
+      extends DefaultBindingTargetVisitor<Object, Collection<Node>> {
+
+    /** Returns a new interface node for the given {@link Binding}. */
+    private InterfaceNode newInterfaceNode(Binding<?> binding) {
+      return new InterfaceNode(NodeId.newTypeId(binding.getKey()), binding.getSource());
+    }
+
+    /**
+     * Returns a new implementation node for the given binding.
+     *
+     * @param binding binding for the node to create
+     * @param members members to add to the node
+     * @return implementation node for the given binding
+     */
+    private ImplementationNode newImplementationNode(Binding<?> binding,
+        Collection<Member> members) {
+      return new ImplementationNode(NodeId.newTypeId(binding.getKey()), binding.getSource(),
+          members);
+    }
+
+    /**
+     * Returns a new instance node for the given {@link Binding}.
+     *
+     * @param binding binding for the node to create
+     * @param instance value of the instance
+     * @return instance node for the given binding
+     */
+    private <T extends Binding<?> & HasDependencies> InstanceNode newInstanceNode(T binding,
+        Object instance) {
+      Collection<Member> members = Lists.newArrayList();
+      for (Dependency<?> dependency : binding.getDependencies()) {
+        InjectionPoint injectionPoint = dependency.getInjectionPoint();
+
+        if (injectionPoint != null) {
+          members.add(injectionPoint.getMember());
+        }
+      }
+      return new InstanceNode(NodeId.newInstanceId(binding.getKey()), binding.getSource(), instance,
+          members);
+    }
+
+    /**
+     * Visitor for {@link ConstructorBinding}s. These are for classes that Guice will instantiate to
+     * satisfy injection requests.
+     */
+    @Override public Collection<Node> visit(ConstructorBinding<?> binding) {
+      Collection<Member> members = Lists.newArrayList();
+      members.add(binding.getConstructor().getMember());
+      for (InjectionPoint injectionPoint : binding.getInjectableMembers()) {
+        members.add(injectionPoint.getMember());
+      }
+
+      return ImmutableList.<Node>of(newImplementationNode(binding, members));
+    }
+
+    /**
+     * Visitor for {@link InstanceBinding}. We render two nodes in this case: an interface node for
+     * the binding's {@link Key}, and then an implementation node for the instance {@link Object}
+     * itself.
+     */
+    @Override public Collection<Node> visit(InstanceBinding<?> binding) {
+      return ImmutableList.<Node>of(newInterfaceNode(binding), newInstanceNode(binding,
+          binding.getInstance()));
+    }
+
+    /**
+     * Same as {@link #visit(InstanceBinding)}, but the binding edge is
+     * {@link BindingEdgeType#PROVIDER}.
+     */
+    @Override public Collection<Node> visit(ProviderInstanceBinding<?> binding) {
+      return ImmutableList.<Node>of(newInterfaceNode(binding), newInstanceNode(binding,
+          binding.getProviderInstance()));
+    }
+
+    @Override public Collection<Node> visitOther(Binding<?> binding) {
+      return ImmutableList.<Node>of(newInterfaceNode(binding));
+    }
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/DefaultRootKeySetCreator.java b/extensions/grapher/src/com/google/inject/grapher/DefaultRootKeySetCreator.java
new file mode 100644
index 0000000..ce73f17
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/DefaultRootKeySetCreator.java
@@ -0,0 +1,46 @@
+/**
+ * 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.Sets;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * Root key set creator that starts with all types that are not Guice internal types or the
+ * {@link Logger} type.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+public class DefaultRootKeySetCreator implements RootKeySetCreator {
+  private static final Key<Logger> loggerKey = Key.get(Logger.class);
+
+  @Override public Set<Key<?>> getRootKeys(Injector injector) {
+    Set<Key<?>> root = Sets.newHashSet();
+    for (Key<?> key : injector.getBindings().keySet()) {
+      if (key.getTypeLiteral().getRawType().getPackage() != Guice.class.getPackage()
+        && !loggerKey.equals(key)) {
+        root.add(key);
+      }
+    }
+    return root;
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/DependencyEdge.java b/extensions/grapher/src/com/google/inject/grapher/DependencyEdge.java
index 5b454a7..94f6316 100644
--- a/extensions/grapher/src/com/google/inject/grapher/DependencyEdge.java
+++ b/extensions/grapher/src/com/google/inject/grapher/DependencyEdge.java
@@ -16,34 +16,49 @@
 
 package com.google.inject.grapher;
 
+import com.google.common.base.Objects;
 import com.google.inject.spi.InjectionPoint;
 
 /**
- * Interface for an edge from a class or {@link InjectionPoint} to the
- * interface node that will satisfy the dependency.
+ * Edge from a class or {@link InjectionPoint} to the interface node that will satisfy the
+ * dependency.
  *
  * @author phopkins@gmail.com (Pete Hopkins)
- *
- * @param <K> The type for node IDs.
  */
-public interface DependencyEdge<K> {
+public class DependencyEdge extends Edge {
   /**
-   * Factory interface for {@link DependencyEdge}s. Renderer implementations
-   * will need to provide an implementation for this.
-   *
-   * @param <K> The type for node IDs.
-   * @param <T> The {@link DependencyEdge} sub-type that this factory provides.
+   * Injection point to which this dependency belongs, or null if the dependency isn't attached to a
+   * particular injection point.
    */
-  interface Factory<K, T extends DependencyEdge<K>> {
-    /**
-     * Creates a new {@link DependencyEdge} and adds it to the graph.
-     *
-     * @param fromId The ID for the class or instance node that has the
-     *     dependency.
-     * @param fromPoint The point where the dependency will be
-     *     {@literal @}{@link Inject}ed. 
-     * @param toId The ID for the interface node that satisfies the dependency.
-     */
-    T newDependencyEdge(K fromId, InjectionPoint fromPoint, K toId);
+  private final InjectionPoint injectionPoint;
+
+  public DependencyEdge(NodeId fromId, NodeId toId, InjectionPoint injectionPoint) {
+    super(fromId, toId);
+    this.injectionPoint = injectionPoint;
+  }
+
+  public InjectionPoint getInjectionPoint() {
+    return injectionPoint;
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (!(obj instanceof DependencyEdge)) {
+      return false;
+    }
+    DependencyEdge other = (DependencyEdge) obj;
+    return super.equals(other) && Objects.equal(injectionPoint, other.injectionPoint);
+  }
+
+  @Override public int hashCode() {
+    return 31 * super.hashCode() + Objects.hashCode(injectionPoint);
+  }
+
+  @Override public String toString() {
+    return "DependencyEdge{fromId=" + getFromId() + " toId=" + getToId()
+        + " injectionPoint=" + injectionPoint + "}";
+  }
+
+  @Override public Edge copy(NodeId fromId, NodeId toId) {
+    return new DependencyEdge(fromId, toId, injectionPoint);
   }
 }
diff --git a/extensions/grapher/src/com/google/inject/grapher/Edge.java b/extensions/grapher/src/com/google/inject/grapher/Edge.java
new file mode 100644
index 0000000..52a1314
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/Edge.java
@@ -0,0 +1,63 @@
+/**
+ * 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.base.Objects;
+
+/**
+ * Edge in a guice dependency graph.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+public abstract class Edge {
+  private final NodeId fromId;
+  private final NodeId toId;
+
+  protected Edge(NodeId fromId, NodeId toId) {
+    this.fromId = fromId;
+    this.toId = toId;
+  }
+
+  public NodeId getFromId() {
+    return fromId;
+  }
+
+  public NodeId getToId() {
+    return toId;
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (!(obj instanceof Edge)) {
+      return false;
+    }
+    Edge other = (Edge) obj;
+    return Objects.equal(fromId, other.fromId) && Objects.equal(toId, other.toId);
+  }
+
+  @Override public int hashCode() {
+    return Objects.hashCode(fromId, toId);
+  }
+
+  /**
+   * Returns a copy of the edge with new node IDs.
+   *
+   * @param fromId new ID of the 'from' node
+   * @param toId new ID of the 'to' node
+   * @return copy of the edge with the new node IDs
+   */
+  public abstract Edge copy(NodeId fromId, NodeId toId);
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/NodeAliasFactory.java b/extensions/grapher/src/com/google/inject/grapher/EdgeCreator.java
similarity index 60%
rename from extensions/grapher/src/com/google/inject/grapher/NodeAliasFactory.java
rename to extensions/grapher/src/com/google/inject/grapher/EdgeCreator.java
index 2e4fef1..3c015b6 100644
--- a/extensions/grapher/src/com/google/inject/grapher/NodeAliasFactory.java
+++ b/extensions/grapher/src/com/google/inject/grapher/EdgeCreator.java
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2008 Google Inc.
+ * 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.
@@ -16,16 +16,16 @@
 
 package com.google.inject.grapher;
 
+import com.google.inject.Binding;
+
 /**
- * Factory for aliasing one node ID to another. Used when we don't want to
- * render {@link Key}s from {@link Binding}s that are not interesting.
- * 
- * @author phopkins@gmail.com (Pete Hopkins)
+ * Creator of graph edges to render. All edges will be rendered on the graph after node aliasing is
+ * performed.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
  */
-public interface NodeAliasFactory<K> {
-  /**
-   * Makes edges that would point to {@code fromId} point to
-   * {@code toId} instead.
-   */
-  void newAlias(K fromId, K toId);
+public interface EdgeCreator {
+
+  /** Returns edges for the given dependency graph. */
+  Iterable<Edge> getEdges(Iterable<Binding<?>> bindings);
 }
diff --git a/extensions/grapher/src/com/google/inject/grapher/GrapherModule.java b/extensions/grapher/src/com/google/inject/grapher/GrapherModule.java
deleted file mode 100644
index 2b945d2..0000000
--- a/extensions/grapher/src/com/google/inject/grapher/GrapherModule.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * Copyright (C) 2008 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.inject.AbstractModule;
-import com.google.inject.Key;
-import com.google.inject.TypeLiteral;
-import com.google.inject.spi.BindingTargetVisitor;
-
-import java.util.Collection;
-
-/**
- * Module for the common bindings for {@link InjectorGrapher}. You will also
- * need to bind a {@link Module} that satisfies the {@link Renderer}
- * dependency.
- * <p>
- * If you want to use subtypes of the node and edge classes, or a different
- * node ID type, you will need to override the {@link GraphingVisitor} binding
- * to specify the new type parameters. 
- *
- * @author phopkins@gmail.com (Pete Hopkins)
- */
-public class GrapherModule extends AbstractModule {
-  @Override
-  protected void configure() {
-    requireBinding(Renderer.class);
-
-    bind(InjectorGrapher.class);
-
-    bind(new TypeLiteral<BindingTargetVisitor<Object, Collection<Key<?>>>>() {})
-        .to(TransitiveDependencyVisitor.class);
-
-    bind(new TypeLiteral<BindingTargetVisitor<Object, Void>>() {})
-        .to(new TypeLiteral<GraphingVisitor<String, InterfaceNode<String>,
-            ImplementationNode<String>, BindingEdge<String>, DependencyEdge<String>>>() {});
-  }
-}
diff --git a/extensions/grapher/src/com/google/inject/grapher/GraphingVisitor.java b/extensions/grapher/src/com/google/inject/grapher/GraphingVisitor.java
deleted file mode 100644
index 41a3be7..0000000
--- a/extensions/grapher/src/com/google/inject/grapher/GraphingVisitor.java
+++ /dev/null
@@ -1,374 +0,0 @@
-/**
- * Copyright (C) 2008 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.inject.Binding;
-import com.google.inject.Inject;
-import com.google.inject.Key;
-import com.google.inject.Provider;
-import com.google.inject.spi.BindingTargetVisitor;
-import com.google.inject.spi.ConstructorBinding;
-import com.google.inject.spi.ConvertedConstantBinding;
-import com.google.inject.spi.Dependency;
-import com.google.inject.spi.ExposedBinding;
-import com.google.inject.spi.HasDependencies;
-import com.google.inject.spi.InjectionPoint;
-import com.google.inject.spi.InstanceBinding;
-import com.google.inject.spi.LinkedKeyBinding;
-import com.google.inject.spi.ProviderBinding;
-import com.google.inject.spi.ProviderInstanceBinding;
-import com.google.inject.spi.ProviderKeyBinding;
-import com.google.inject.spi.UntargettedBinding;
-
-import java.lang.reflect.Member;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * {@link BindingTargetVisitor} that adds nodes and edges to the graph based on
- * the visited {@link Binding}.
- * <p>
- * This class is parameterized over the four graph element types
- * ({@link InterfaceNode}, {@link ImplementationNode}, {@link BindingEdge}, and
- * {@link DependencyEdge}) so that you can extend those interfaces and also
- * extend this class, and the helper methods will all return your new types.
- * 
- * @author phopkins@gmail.com (Pete Hopkins)
- *
- * @param <K> The type for node IDs.
- * @param <N> Type for {@link InterfaceNode}s.
- * @param <M> Type for {@link ImplementationNode}.s
- * @param <B> Type for {@link BindingEdge}s.
- * @param <D> Type for {@link DependencyEdge}s.
- */
-public class GraphingVisitor<K, N extends InterfaceNode<K>, M extends ImplementationNode<K>,
-    B extends BindingEdge<K>, D extends DependencyEdge<K>>
-implements BindingTargetVisitor<Object, Void> {
-
-  private final NodeIdFactory<K> idFactory;
- 
-  private final InterfaceNode.Factory<K, N> interfaceNodeFactory;
-  private final ImplementationNode.Factory<K, M> implementationNodeFactory;
-  private final BindingEdge.Factory<K, B> bindingEdgeFactory;
-  private final DependencyEdge.Factory<K, D> dependencyEdgeFactory;
-  private final NodeAliasFactory<K> nodeAliasFactory;
-
-  @Inject
-  public GraphingVisitor(NodeIdFactory<K> idFactory,
-      InterfaceNode.Factory<K, N> interfaceNodeFactory,
-      ImplementationNode.Factory<K, M> implementationNodeFactory,
-      BindingEdge.Factory<K, B> bindingEdgeFactory,
-      DependencyEdge.Factory<K, D> dependencyEdgeFactory,
-      NodeAliasFactory<K> nodeAliasFactory) {
-    this.idFactory = idFactory;
-    this.interfaceNodeFactory = interfaceNodeFactory;
-    this.implementationNodeFactory = implementationNodeFactory;
-    this.bindingEdgeFactory = bindingEdgeFactory;
-    this.dependencyEdgeFactory = dependencyEdgeFactory;
-    this.nodeAliasFactory = nodeAliasFactory;
-  }
-
-  /**
-   * Helper method to return the standard node ID for the {@link Binding}'s
-   * {@link Key}.
-   * 
-   * @see NodeIdFactory#getClassNodeId(Key)
-   */
-  protected final K getClassNodeId(Binding<?> binding) {
-    return idFactory.getClassNodeId(binding.getKey());
-  }
-
-  /**
-   * Helper method to return the instance node ID for the {@link Binding}'s
-   * {@link Key}.
-   * 
-   * @see NodeIdFactory#getInstanceNodeId(Key)
-   */
-  protected final K getInstanceNodeId(Binding<?> binding) {
-    return idFactory.getInstanceNodeId(binding.getKey());
-  }
-
-  /**
-   * Creates and returns a new {@link InterfaceNode} object for the given
-   * {@link Binding}.
-   */
-  protected N newInterfaceNode(Binding<?> binding) {
-    N node = interfaceNodeFactory.newInterfaceNode(getClassNodeId(binding));
-    node.setKey(binding.getKey());
-    node.setSource(binding.getSource());
-
-    return node;
-  }
-
-  /**
-   * Creates and returns a new {@link ImplementationNode} for the given
-   * {@link Binding}, where the {@link Binding} is for a class that Guice
-   * will instantiate, rather than a specific instance.
-   */
-  protected M newClassImplementationNode(Binding<?> binding,
-      InjectionPoint constructorInjectionPoint,
-      Collection<InjectionPoint> memberInjectionPoints) {
-    M node = implementationNodeFactory.newImplementationNode(getClassNodeId(binding));
-    node.setClassKey(binding.getKey());
-    // we don't set the source here because it's not interesting for classes
-
-    node.addMember(constructorInjectionPoint.getMember());
-    for (InjectionPoint injectionPoint : memberInjectionPoints) {
-      node.addMember(injectionPoint.getMember());
-    }
-
-    return node;
-  }
-
-  /**
-   * Creates and returns a new {@link ImplementationNode} for the given
-   * {@link Binding}, where the {@link Binding} is for an instance, rather than
-   * a class.
-   */
-  protected M newInstanceImplementationNode(Binding<?> binding, Object instance) {
-    M node = implementationNodeFactory.newImplementationNode(getInstanceNodeId(binding));
-    node.setSource(binding.getSource());
-    node.setInstance(instance);
-
-    return node;
-  }
-
-  /**
-   * Creates a new {@link BindingEdge} from the given node to the specified
-   * node.
-   *
-   * @param nodeId ID of the {@link InterfaceNode} that binds to the other.
-   * @param toId The node ID of a class or instance that is bound.
-   * @param type The {@link BindingEdge.Type} of this binding.
-   * @return The newly-created and added {@link BindingEdge}.
-   */
-  protected B newBindingEdge(K nodeId, K toId, BindingEdge.Type type) {
-    B edge = bindingEdgeFactory.newBindingEdge(nodeId, toId);
-    edge.setType(type);
-
-    return edge;
-  }
-
-  /**
-   * Adds {@link DependencyEdge}s to the graph for each of the provided
-   * {@link Dependency}s. These will be from the given node ID to the
-   * {@link Dependency}'s {@link Key}.
-   * <p>
-   * If a {@link Dependency} has an associated {@link InjectionPoint}, its
-   * member will be added to the given {@link ImplementationNode} and the edge
-   * will start at the {@link Member}.
-   *
-   * @see #newDependencyEdge(Object, InjectionPoint, Dependency)
-   * 
-   * @param nodeId ID of the node that should be the tail of the
-   *     {@link DependencyEdge}s.
-   * @param node An {@link ImplementationNode} to add {@link Member}s to.
-   * @param dependencies {@link Collection} of {@link Dependency}s from the
-   *     {@link Binding}.
-   * @return A {@link Collection} of the {@link DependencyEdge}s that were
-   *     added to the graph.
-   */
-  protected Collection<D> newDependencyEdges(K nodeId, M node,
-      Collection<Dependency<?>> dependencies) {
-    List<D> edges = Lists.newArrayList();
-
-    for (Dependency<?> dependency : dependencies) {
-      InjectionPoint injectionPoint = dependency.getInjectionPoint();
-
-      if (injectionPoint != null) {
-        node.addMember(injectionPoint.getMember());
-      }
-
-      D edge = newDependencyEdge(nodeId, injectionPoint, dependency);
-      edges.add(edge);
-    }
-
-    return edges;
-  }
-
-  /**
-   * Creates a new {@link DependencyEdge} from the given node to a
-   * {@link Dependency}.
-   * <p>
-   * This method takes more comprehensive parameters than strictly necessary
-   * in case they would be useful to overriding implementations.
-   *
-   * @param nodeId ID of the {@link ImplementationNode} where the edges will start.
-   * @param injectionPoint The {@link InjectionPoint} that gave rise to this
-   *     {@link Dependency}, if one exists. Used to figure out which
-   *     {@link Member} the edge should point from.
-   * @param dependency The {@link Dependency} to represent with this edge.
-   * @return The newly-created and added {@link DependencyEdge}.
-   */
-  protected D newDependencyEdge(K nodeId,
-      InjectionPoint injectionPoint, Dependency<?> dependency) {
-    K toId = idFactory.getClassNodeId(dependency.getKey());
-    return dependencyEdgeFactory.newDependencyEdge(nodeId, injectionPoint, toId);
-  }
-
-
-  /**
-   * Visitor for {@link ConstructorBinding}s. These are for classes that Guice
-   * will instantiate to satisfy injection requests. We create a new
-   * {@link ImplementationNode} for the class, then add edges to everything
-   * that it depends on to be instantiated.
-   *
-   * @see #newClassImplementationNode(Binding, InjectionPoint, Collection)
-   * @see #newDependencyEdges(Object, ImplementationNode, Collection)
-   */
-  public Void visit(ConstructorBinding<?> binding) {
-    M node = newClassImplementationNode(binding, binding.getConstructor(),
-        binding.getInjectableMembers());
-    newDependencyEdges(getClassNodeId(binding), node, binding.getDependencies());
-
-    return null;
-  }
-
-  /**
-   * Visitor for {@link ConvertedConstantBinding}. The {@link Binding}'s
-   * {@link Key} will be of an annotated primitive type, and the value of
-   * {@link ConvertedConstantBinding#getSourceKey()} will be of a
-   * {@link String} with the same annotation.
-   * <p>
-   * We render this as an {@link InterfaceNode} that has a
-   * {@link BindingEdge} to the source {@link Key}. That will then be rendered
-   * by {@link #visit(InstanceBinding)} as an {@link InterfaceNode}
-   * with a {@link BindingEdge} to the {@link String} instance.
-   * 
-   * @see #newInterfaceNode(Binding)
-   * @see #newBindingEdge(Object, Object, com.google.inject.grapher.BindingEdge.Type)
-   */
-  public Void visit(ConvertedConstantBinding<?> binding) {
-    newInterfaceNode(binding);
-    newBindingEdge(getClassNodeId(binding), idFactory.getClassNodeId(binding.getSourceKey()),
-        BindingEdge.Type.CONVERTED_CONSTANT);
-
-    return null;
-  }
-
-  /**
-   * Currently not displayed on the graph.
-   */
-  public Void visit(ExposedBinding<?> binding) {
-    // TODO(phopkins): Decide if this is needed for graphing.
-    return null;
-  }
-
-  /**
-   * Visitor for {@link InstanceBinding}. We render two nodes in this case: a
-   * {@link InterfaceNode} for the binding's {@link Key}, and then an
-   * {@link ImplementationNode} for the instance {@link Object} itself. We run
-   * a binding node between them.
-   * <p>
-   * We then render any {@link DependencyEdge}s that the instance may have,
-   * which come either from {@link InjectionPoint}s (method and field) on the
-   * instance, or on {@link Dependency}s the instance declares through the
-   * {@link HasDependencies} interface.
-   * 
-   * @see #newInterfaceNode(Binding)
-   * @see #newBindingEdge(Object, Object, com.google.inject.grapher.BindingEdge.Type)
-   * @see #newInstanceImplementationNode(Binding, Object)
-   * @see #newDependencyEdges(Object, ImplementationNode, java.util.Collection)
-   */
-  public Void visit(InstanceBinding<?> binding) {
-    newInterfaceNode(binding);
-    newBindingEdge(getClassNodeId(binding), getInstanceNodeId(binding),
-        BindingEdge.Type.NORMAL);
-
-    M node = newInstanceImplementationNode(binding, binding.getInstance());
-    newDependencyEdges(getInstanceNodeId(binding), node, binding.getDependencies());
-
-    return null;
-  }
-
-  /**
-   * Visitor for {@link LinkedKeyBinding}. This is the standard {@link Binding}
-   * you get from binding an interface class to an implementation class. We
-   * create an {@link InterfaceNode}, then draw a {@link BindingEdge} to the
-   * node of the implementing class.
-   * 
-   * @see #newInterfaceNode(Binding)
-   * @see #newBindingEdge(Object, Object, com.google.inject.grapher.BindingEdge.Type)
-   */
-  public Void visit(LinkedKeyBinding<?> binding) {
-    newInterfaceNode(binding);
-    newBindingEdge(getClassNodeId(binding), idFactory.getClassNodeId(binding.getLinkedKey()), 
-        BindingEdge.Type.NORMAL);
-
-    return null;
-  }
-
-  /**
-   * Visitor for {@link ProviderBinding}. These {@link Binding}s arise from an
-   * {@link InjectionPoint} for the {@link Provider} interface. Since this
-   * isn't tremendously interesting information, we don't render this binding
-   * on the graph, and instead let the {@link DependencyEdge} go straight from
-   * the {@link InjectionPoint} to the node specified by
-   * {@link ProviderBinding#getProvidedKey()}.
-   * 
-   * @see NodeAliasFactory#newAlias(Object, Object)
-   */
-  public Void visit(ProviderBinding<?> binding) {
-    nodeAliasFactory.newAlias(getClassNodeId(binding),
-        idFactory.getClassNodeId(binding.getProvidedKey()));
-
-    return null;
-  }
-
-  /**
-   * Same as {@link #visit(InstanceBinding)}, but the
-   * {@link BindingEdge} is {@link BindingEdge.Type#PROVIDER}.
-   * 
-   * @see #newInterfaceNode(Binding)
-   * @see #newBindingEdge(Object, Object, com.google.inject.grapher.BindingEdge.Type)
-   * @see #newInstanceImplementationNode(Binding, Object)
-   * @see #newDependencyEdges(Object, ImplementationNode, java.util.Collection)
-   */
-  public Void visit(ProviderInstanceBinding<?> binding) {
-    newInterfaceNode(binding);
-    newBindingEdge(getClassNodeId(binding), getInstanceNodeId(binding), BindingEdge.Type.PROVIDER);
-
-    M node = newInstanceImplementationNode(binding, binding.getProviderInstance());
-    newDependencyEdges(getInstanceNodeId(binding), node, binding.getDependencies());
-
-    return null;
-  }
-
-  /**
-   * Same as {@link #visit(LinkedKeyBinding)}, but the
-   * {@link BindingEdge} is {@link BindingEdge.Type#PROVIDER}.
-   * 
-   * @see #newInterfaceNode(Binding)
-   * @see #newBindingEdge(Object, Object, com.google.inject.grapher.BindingEdge.Type)
-   */
-  public Void visit(ProviderKeyBinding<?> binding) {
-    newInterfaceNode(binding);
-    newBindingEdge(getClassNodeId(binding), idFactory.getClassNodeId(binding.getProviderKey()),
-        BindingEdge.Type.PROVIDER);
-
-    return null;
-  }
-
-  /**
-   * Currently not displayed on the graph.
-   */
-  public Void visit(UntargettedBinding<?> binding) {
-    // TODO(phopkins): Decide if this is needed for graphing.
-    return null;
-  }
-}
diff --git a/extensions/grapher/src/com/google/inject/grapher/ImplementationNode.java b/extensions/grapher/src/com/google/inject/grapher/ImplementationNode.java
index 1cbd185..b4a67a0 100644
--- a/extensions/grapher/src/com/google/inject/grapher/ImplementationNode.java
+++ b/extensions/grapher/src/com/google/inject/grapher/ImplementationNode.java
@@ -16,52 +16,48 @@
 
 package com.google.inject.grapher;
 
-import com.google.inject.Key;
-
+import com.google.common.base.Objects;
 import java.lang.reflect.Member;
+import java.util.Collection;
 
 /**
- * Node for classes and instances that have {@link Dependency}s and are
- * bound to {@link InterfaceNode}s. These nodes will often have fields for
- * {@link Member}s that are {@link InjectionPoint}s.
- * 
+ * Node for types that have {@link Dependency}s and are bound to {@link InterfaceNode}s. These
+ * nodes will often have fields for {@link Member}s that are {@link InjectionPoint}s.
+ *
  * @see DependencyEdge
  *
  * @author phopkins@gmail.com (Pete Hopkins)
- *
- * @param <K> The type for node IDs.
  */
-public interface ImplementationNode<K> {
-  /**
-   * Sets the {@link Key} that this node is for. Used when the node is
-   * representing a class that Guice will instantiate.
-   */
-  void setClassKey(Key<?> key);
+public class ImplementationNode extends Node {
+  private final Collection<Member> members;
 
-  /**
-   * Sets the {@link Object} that's the already-created instance. Used when
-   * this node is represeting the instance instead of a class.
-   */
-  void setInstance(Object instance);
+  public ImplementationNode(NodeId id, Object source, Collection<Member> members) {
+    super(id, source);
+    this.members = members;
+  }
 
-  void setSource(Object source);
-  void addMember(Member member);
+  public Collection<Member> getMembers() {
+    return members;
+  }
 
-  /**
-   * Factory interface for {@link ImplementationNode}s. Renderer
-   * implementations will need to provide an implementation for this.
-   *
-   * @param <K> The type for node IDs.
-   * @param <T> The {@link ImplementationNode} sub-type that this factory
-   *     provides.
-   */
-  interface Factory<K, T extends ImplementationNode<K>> {
-    /**
-     * Creates a new {@link ImplementationNode} and adds it to the graph.
-     *
-     * @param nodeId ID for the node.
-     * @return The new {@link ImplementationNode} instance.
-     */
-    T newImplementationNode(K nodeId);
+  @Override public boolean equals(Object obj) {
+    if (!(obj instanceof ImplementationNode)) {
+      return false;
+    }
+    ImplementationNode other = (ImplementationNode) obj;
+    return super.equals(other) && Objects.equal(members, other.members);
+  }
+
+  @Override public int hashCode() {
+    return 31 * super.hashCode() + Objects.hashCode(members);
+  }
+
+  @Override public String toString() {
+    return "ImplementationNode{id=" + getId() + " source=" + getSource()
+        + " members=" + members + "}";
+  }
+
+  @Override public Node copy(NodeId id) {
+    return new ImplementationNode(id, getSource(), getMembers());
   }
 }
diff --git a/extensions/grapher/src/com/google/inject/grapher/InjectorGrapher.java b/extensions/grapher/src/com/google/inject/grapher/InjectorGrapher.java
index bb70fd6..91fd92c 100644
--- a/extensions/grapher/src/com/google/inject/grapher/InjectorGrapher.java
+++ b/extensions/grapher/src/com/google/inject/grapher/InjectorGrapher.java
@@ -16,151 +16,26 @@
 
 package com.google.inject.grapher;
 
-import com.google.common.collect.Sets;
-import com.google.inject.Binding;
-import com.google.inject.Guice;
-import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.Key;
-import com.google.inject.spi.BindingTargetVisitor;
 
 import java.io.IOException;
-import java.util.Collection;
-import java.util.Iterator;
 import java.util.Set;
-import java.util.logging.Logger;
 
 /**
- * Root class for graphing an {@link Injector}. Bound in {@link GrapherModule}.
- * <p>
- * Use {@link #of(Injector)} to specify the {@link Injector} to use, and
- * {@link graph()} to graph the {@link Injector} using the currently-bound
- * {@link Renderer}.
- * <p>
- * By default, this will graph the entire {@link Injector}. Use
- * {@link #rootedAt(Class...)} or {@link #rootedAt(Key...)} to specify an
- * initial set of {@link Class}es or {@link Key}s to use, and this will graph
- * their transitive bindings and dependencies.
+ * Guice injector grapher. Renders the guice dependency graph for an injector. It can render the
+ * whole dependency graph or just transitive dependencies of a given set of nodes.
  *
  * @author phopkins@gmail.com (Pete Hopkins)
  */
-public class InjectorGrapher {  
-  private static final Key<Logger> loggerKey = Key.get(Logger.class);
+public interface InjectorGrapher {
 
-  private final BindingTargetVisitor<Object, Collection<Key<?>>> keyVisitor;
-  private final BindingTargetVisitor<Object, Void> graphingVisitor;
-  private final Renderer renderer;
-  
-  private Injector injector;
-  private Set<Key<?>> root;
-  
-  @Inject
-  public InjectorGrapher(BindingTargetVisitor<Object, Collection<Key<?>>> keyVisitor,
-      BindingTargetVisitor<Object, Void> graphingVisitor, Renderer renderer) {
-    this.keyVisitor = keyVisitor;
-    this.graphingVisitor = graphingVisitor;
-    this.renderer = renderer;
-  }
+  /** Graphs the guice dependency graph for the given injector using default starting keys. */
+  void graph(Injector injector) throws IOException;
 
   /**
-   * Sets the {@link Injector} to graph.
+   * Graphs the guice dependency graph for the given injector using the given starting keys and
+   * their transitive dependencies.
    */
-  public InjectorGrapher of(Injector injector) {
-    this.injector = injector;
-    return this;
-  }
-
-  /**
-   * Sets an initial group of {@link Class}es to use as the starting point for
-   * the graph. The graph will be of these classes and their transitive
-   * dependencies and bindings.
-   */
-  public InjectorGrapher rootedAt(Class<?>... classes) {
-    this.root = Sets.newHashSet();
-    
-    for (Class<?> clazz : classes) {
-      this.root.add(Key.get(clazz));
-    }
-    
-    return this;
-  }
-
-  /**
-   * Sets an initial group of {@link Key}s to use as the starting point for
-   * the graph. The graph will be of these keys and their transitive
-   * dependencies and bindings.
-   */
-  public InjectorGrapher rootedAt(Key<?>... keys) {
-    this.root = Sets.newHashSet();
-
-    for (Key<?> key : keys) {
-      this.root.add(key);
-    }
-
-    return this;
-  }
-
-  /**
-   * Renders a graph with the bound {@link Renderer}. The {@link Injector}
-   * must have already been specified with {@link #of(Injector)}.
-   */
-  public void graph() throws IOException {
-    processBindings();
-    renderer.render();
-  }
-
-  /**
-   * Tests for {@link Key}s that we don't want to include by default in the
-   * graph. They're left out of the initial set, but will appear if depended
-   * upon by other classes. Leaves out Guice classes (such as the
-   * {@link Injector}) and the {@link Logger}.
-   */
-  private boolean skipKey(Key<?> key) {
-    return key.getTypeLiteral().getRawType().getPackage() == Guice.class.getPackage()
-        || loggerKey.equals(key);
-  }
-
-  /**
-   * Takes the set of starting {@link Binding}s, which comes either from the
-   * {@link Injector} or from {@link #rootedAt(Class...)}, and applies the
-   * {@link #graphingVisitor} to them. Uses the {@link #keyVisitor} to build
-   * out the set of {@link Key}s so that the graph covers the transitive
-   * dependencies and bindings.
-   */
-  private void processBindings() {
-    Set<Key<?>> keys = Sets.newLinkedHashSet();
-    Set<Key<?>> visitedKeys = Sets.newHashSet();
-
-    // build up the root set from the Injector if it wasn't specified
-    if (root == null) {
-      for (Key<?> key : injector.getBindings().keySet()) {
-        if (!skipKey(key)) {
-          keys.add(key);
-        }
-      }
-    } else {
-      keys.addAll(root);
-    }
-    
-    while (!keys.isEmpty()) {
-      Iterator<Key<?>> iterator = keys.iterator();
-      Key<?> key = iterator.next();
-      iterator.remove();
-      
-      if (visitedKeys.contains(key)) {
-        continue;
-      }
-      
-      Binding<?> binding = injector.getBinding(key);
-      visitedKeys.add(key);
-
-      binding.acceptTargetVisitor(graphingVisitor);
-
-      // find the dependencies and make sure that they get visited
-      Collection<Key<?>> newKeys = binding.acceptTargetVisitor(keyVisitor);
-      if (newKeys != null) {
-        keys.addAll(newKeys);
-      }
-    }
-  }
+  void graph(Injector injector, Set<Key<?>> root) throws IOException;
 }
diff --git a/extensions/grapher/src/com/google/inject/grapher/InstanceNode.java b/extensions/grapher/src/com/google/inject/grapher/InstanceNode.java
new file mode 100644
index 0000000..cda56bc
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/InstanceNode.java
@@ -0,0 +1,66 @@
+/**
+ * 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.base.Objects;
+import java.lang.reflect.Member;
+
+/**
+ * Node for instances. Used when a type is bound to an instance.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+public class InstanceNode extends Node {
+  private final Object instance;
+  private final Iterable<Member> members;
+
+  public InstanceNode(NodeId id, Object source, Object instance, Iterable<Member> members) {
+    super(id, source);
+    this.instance = instance;
+    this.members = members;
+  }
+
+  public Object getInstance() {
+    return instance;
+  }
+
+  public Iterable<Member> getMembers() {
+    return members;
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (!(obj instanceof InstanceNode)) {
+      return false;
+    }
+    InstanceNode other = (InstanceNode) obj;
+    return super.equals(other) && Objects.equal(instance, other.instance)
+        && Objects.equal(members, other.members);
+  }
+
+  @Override public int hashCode() {
+    return 31 * super.hashCode() + Objects.hashCode(instance, members);
+  }
+
+  @Override public String toString() {
+    return "InstanceNode{id=" + getId() + " source=" + getSource() + " instance=" + instance
+        + " members=" + members + "}";
+  }
+
+  @Override public Node copy(NodeId id) {
+    return new InstanceNode(id, getSource(), getInstance(), getMembers());
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/InterfaceNode.java b/extensions/grapher/src/com/google/inject/grapher/InterfaceNode.java
index 0e343e5..58ae2f0 100644
--- a/extensions/grapher/src/com/google/inject/grapher/InterfaceNode.java
+++ b/extensions/grapher/src/com/google/inject/grapher/InterfaceNode.java
@@ -16,36 +16,27 @@
 
 package com.google.inject.grapher;
 
-import com.google.inject.Key;
-
 /**
- * Node for an interface class that has been bound to an implementation class
- * or instance. These nodes are basically defined by a {@link Key}.
+ * Node for an interface type that has been bound to an implementation class or instance.
  *
  * @see BindingEdge
  *
  * @author phopkins@gmail.com (Pete Hopkins)
- *
- * @param <K> The type for node IDs.
  */
-public interface InterfaceNode<K> {
-  void setKey(Key<?> key);
-  void setSource(Object source);
+public class InterfaceNode extends Node {
+  public InterfaceNode(NodeId id, Object source) {
+    super(id, source);
+  }
 
-  /**
-   * Factory interface for {@link InterfaceNode}s. Renderer implementations
-   * will need to provide an implementation for this.
-   *
-   * @param <K> The type for node IDs.
-   * @param <T> The {@link InterfaceNode} sub-type that this factory provides.
-   */
-  interface Factory<K, T extends InterfaceNode<K>> {
-    /**
-     * Creates a new {@link InterfaceNode} and adds it to the graph.
-     *
-     * @param nodeId ID for the node.
-     * @return The new {@link InterfaceNode} instance.
-     */
-    T newInterfaceNode(K nodeId);
+  @Override public Node copy(NodeId id) {
+    return new InterfaceNode(id, getSource());
+  }
+
+  @Override public boolean equals(Object obj) {
+    return (obj instanceof InterfaceNode) && super.equals(obj);
+  }
+
+  @Override public String toString() {
+    return "InterfaceNode{id=" + getId() + " source=" + getSource() + "}";
   }
 }
diff --git a/extensions/grapher/src/com/google/inject/grapher/Node.java b/extensions/grapher/src/com/google/inject/grapher/Node.java
new file mode 100644
index 0000000..870f805
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/Node.java
@@ -0,0 +1,69 @@
+/**
+ * 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.base.Objects;
+
+/**
+ * Node in a guice dependency graph.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+public abstract class Node {
+  /**
+   * When set to true, the source object is ignored in {@link #equals} and {@link #hashCode}.
+   * Only used in tests.
+   */
+  static boolean ignoreSourceInComparisons = false;
+
+  private final NodeId id;
+  private final Object source;
+
+  protected Node(NodeId id, Object source) {
+    this.id = id;
+    this.source = source;
+  }
+
+  public NodeId getId() {
+    return id;
+  }
+
+  public Object getSource() {
+    return source;
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (!(obj instanceof Node)) {
+      return false;
+    }
+    Node other = (Node) obj;
+    return Objects.equal(id, other.id)
+        && (ignoreSourceInComparisons || Objects.equal(source, other.source));
+  }
+
+  @Override public int hashCode() {
+    return ignoreSourceInComparisons ? id.hashCode() : Objects.hashCode(id, source);
+  }
+
+  /**
+   * Returns a copy of the node with a new ID.
+   *
+   * @param id new ID of the node
+   * @return copy of the node with a new ID
+   */
+  public abstract Node copy(NodeId id);
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/NodeAliasFactory.java b/extensions/grapher/src/com/google/inject/grapher/NodeCreator.java
similarity index 60%
copy from extensions/grapher/src/com/google/inject/grapher/NodeAliasFactory.java
copy to extensions/grapher/src/com/google/inject/grapher/NodeCreator.java
index 2e4fef1..e22688f 100644
--- a/extensions/grapher/src/com/google/inject/grapher/NodeAliasFactory.java
+++ b/extensions/grapher/src/com/google/inject/grapher/NodeCreator.java
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2008 Google Inc.
+ * 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.
@@ -16,16 +16,15 @@
 
 package com.google.inject.grapher;
 
+import com.google.inject.Binding;
+
 /**
- * Factory for aliasing one node ID to another. Used when we don't want to
- * render {@link Key}s from {@link Binding}s that are not interesting.
- * 
- * @author phopkins@gmail.com (Pete Hopkins)
+ * Creator of graph nodes.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
  */
-public interface NodeAliasFactory<K> {
-  /**
-   * Makes edges that would point to {@code fromId} point to
-   * {@code toId} instead.
-   */
-  void newAlias(K fromId, K toId);
+public interface NodeCreator {
+
+  /** Returns nodes for the given dependency graph. */
+  Iterable<Node> getNodes(Iterable<Binding<?>> bindings);
 }
diff --git a/extensions/grapher/src/com/google/inject/grapher/NodeId.java b/extensions/grapher/src/com/google/inject/grapher/NodeId.java
new file mode 100644
index 0000000..eaf3f15
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/NodeId.java
@@ -0,0 +1,77 @@
+/**
+ * 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.base.Objects;
+import com.google.inject.Key;
+
+/**
+ * ID of a node in the graph. An ID is given by a {@link Key} and a node type, which is used to
+ * distinguish instances and implementation classes for the same key. For example
+ * {@code bind(Integer.class).toInstance(42)} produces two nodes: an
+ * interface node with the key of {@code Key<Integer>} and an instance node with the same
+ * {@link Key} and value of 42.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+public final class NodeId {
+
+  /** Type of node. */
+  public enum NodeType {
+    /** Type or class node. */
+    TYPE,
+
+    /** Instance node, used when something is bound to an instance. */
+    INSTANCE
+  }
+
+  private final Key<?> key;
+  private final NodeType nodeType;
+
+  private NodeId(Key<?> key, NodeType nodeType) {
+    this.key = key;
+    this.nodeType = nodeType;
+  }
+
+  public static NodeId newTypeId(Key<?> key) {
+    return new NodeId(key, NodeType.TYPE);
+  }
+
+  public static NodeId newInstanceId(Key<?> key) {
+    return new NodeId(key, NodeType.INSTANCE);
+  }
+
+  public Key<?> getKey() {
+    return key;
+  }
+
+  @Override public int hashCode() {
+    return Objects.hashCode(key, nodeType);
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (!(obj.getClass().equals(NodeId.class))) {
+      return false;
+    }
+    NodeId other = (NodeId) obj;
+    return Objects.equal(key, other.key) && Objects.equal(nodeType, other.nodeType);
+  }
+
+  @Override public String toString() {
+    return "NodeId{nodeType=" + nodeType + " key=" + key + "}";
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/NodeIdFactory.java b/extensions/grapher/src/com/google/inject/grapher/NodeIdFactory.java
deleted file mode 100644
index a61e24b..0000000
--- a/extensions/grapher/src/com/google/inject/grapher/NodeIdFactory.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Copyright (C) 2008 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.inject.Key;
-
-/**
- * Factory for abstract identifiers for elements on the graph. Most graph nodes
- * will correspond directly to {@link Key}s, but we do this for additional
- * flexibility and because instances do not have separate {@link Key}s from the
- * interfaces they are bound to.
- * <p>
- * Node IDs are treated as opaque values by {@link GraphingVisitor} and the
- * other classes in this package.
- *
- * @author phopkins@gmail.com (Pete Hopkins)
- * 
- * @param <K> The type for node IDs.
- */
-public interface NodeIdFactory<K> {
-  K getClassNodeId(Key<?> key);
-  K getInstanceNodeId(Key<?> key);
-}
diff --git a/extensions/grapher/src/com/google/inject/grapher/ProviderAliasCreator.java b/extensions/grapher/src/com/google/inject/grapher/ProviderAliasCreator.java
new file mode 100644
index 0000000..d55ea7f
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/ProviderAliasCreator.java
@@ -0,0 +1,43 @@
+/**
+ * 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.inject.Binding;
+import com.google.inject.spi.ProviderBinding;
+import java.util.List;
+
+/**
+ * Alias creator that creates an alias for each {@link ProviderBinding}. These {@link Binding}s
+ * arise from an {@link InjectionPoint} for the {@link Provider} interface. Since this isn't
+ * very interesting information, we don't render this binding on the graph, and just alias the two
+ * nodes.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+final class ProviderAliasCreator implements AliasCreator {
+  @Override public Iterable<Alias> createAliases(Iterable<Binding<?>> bindings) {
+    List<Alias> aliases = Lists.newArrayList();
+    for (Binding<?> binding : bindings) {
+      if (binding instanceof ProviderBinding) {
+        aliases.add(new Alias(NodeId.newTypeId(binding.getKey()),
+            NodeId.newTypeId(((ProviderBinding<?>) binding).getProvidedKey())));
+      }
+    }
+    return aliases;
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/Renderer.java b/extensions/grapher/src/com/google/inject/grapher/Renderer.java
deleted file mode 100644
index a5e6b77..0000000
--- a/extensions/grapher/src/com/google/inject/grapher/Renderer.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * Copyright (C) 2008 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 java.io.IOException;
-
-/**
- * Interface for the service that renders the graph. It is assumed that the
- * implementations for {@link BindingEdge.Factory},
- * {@link ImplementationNode.Factory}, etc. will have direct access to the
- * internals of the {@link Renderer} implementation, so the only external
- * interface needed is the {@link #render()} call that {@link InjectorGrapher}
- * calls when it's done.
- *
- * @author phopkins@gmail.com (Pete Hopkins)
- */
-public interface Renderer {
-  void render() throws IOException;
-}
diff --git a/extensions/grapher/src/com/google/inject/grapher/RootKeySetCreator.java b/extensions/grapher/src/com/google/inject/grapher/RootKeySetCreator.java
new file mode 100644
index 0000000..f9e37d3
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/RootKeySetCreator.java
@@ -0,0 +1,33 @@
+/**
+ * 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.inject.Injector;
+import com.google.inject.Key;
+import java.util.Set;
+
+/**
+ * Creator of the default starting set of keys to graph. These keys and their transitive
+ * dependencies will be graphed.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+public interface RootKeySetCreator {
+
+  /** Returns the set of starting keys to graph. */
+  Set<Key<?>> getRootKeys(Injector injector);
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/StringNodeIdFactory.java b/extensions/grapher/src/com/google/inject/grapher/StringNodeIdFactory.java
deleted file mode 100644
index 2b2a115..0000000
--- a/extensions/grapher/src/com/google/inject/grapher/StringNodeIdFactory.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * Copyright (C) 2008 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.inject.Key;
-
-/**
- * {@link IdFactory} implementation that for {@link String} node IDs. The IDs
- * are comprised of letters, numbers and underscores.
- *
- * @author phopkins@gmail.com (Pete Hopkins)
- */
-public class StringNodeIdFactory implements NodeIdFactory<String> {
-  public String getClassNodeId(Key<?> key) {
-    return "k_" + Integer.toHexString(key.hashCode());
-  }
-
-  public String getInstanceNodeId(Key<?> key) {
-    return "i_" + Integer.toHexString(key.hashCode());
-  }
-}
diff --git a/extensions/grapher/src/com/google/inject/grapher/TransitiveDependencyVisitor.java b/extensions/grapher/src/com/google/inject/grapher/TransitiveDependencyVisitor.java
index 27deaa6..51ba1cc 100644
--- a/extensions/grapher/src/com/google/inject/grapher/TransitiveDependencyVisitor.java
+++ b/extensions/grapher/src/com/google/inject/grapher/TransitiveDependencyVisitor.java
@@ -18,19 +18,18 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import com.google.inject.Binding;
 import com.google.inject.Key;
-import com.google.inject.spi.BindingTargetVisitor;
 import com.google.inject.spi.ConstructorBinding;
 import com.google.inject.spi.ConvertedConstantBinding;
+import com.google.inject.spi.DefaultBindingTargetVisitor;
 import com.google.inject.spi.Dependency;
-import com.google.inject.spi.ExposedBinding;
 import com.google.inject.spi.HasDependencies;
 import com.google.inject.spi.InstanceBinding;
 import com.google.inject.spi.LinkedKeyBinding;
 import com.google.inject.spi.ProviderBinding;
 import com.google.inject.spi.ProviderInstanceBinding;
 import com.google.inject.spi.ProviderKeyBinding;
-import com.google.inject.spi.UntargettedBinding;
 
 import java.util.Collection;
 import java.util.Set;
@@ -38,13 +37,13 @@
 /**
  * {@link BindingTargetVisitor} that returns a {@link Collection} of the
  * {@link Key}s of each {@link Binding}'s dependencies. Used by
- * {@link InjectorGropher} to walk the dependency graph from a starting set of
+ * {@link InjectorGrapher} to walk the dependency graph from a starting set of
  * {@link Binding}s.
  *
  * @author phopkins@gmail.com (Pete Hopkins)
  */
 public class TransitiveDependencyVisitor
-implements BindingTargetVisitor<Object, Collection<Key<?>>> {
+extends DefaultBindingTargetVisitor<Object, Collection<Key<?>>> {
 
   private Collection<Key<?>> visitHasDependencies(HasDependencies hasDependencies) {
     Set<Key<?>> dependencies = Sets.newHashSet();
@@ -56,41 +55,35 @@
     return dependencies;
   }
   
-  public Collection<Key<?>> visit(ConstructorBinding<?> binding) {
+  @Override public Collection<Key<?>> visit(ConstructorBinding<?> binding) {
     return visitHasDependencies(binding);
   }
 
-  public Collection<Key<?>> visit(ConvertedConstantBinding<?> binding) {
+  @Override public Collection<Key<?>> visit(ConvertedConstantBinding<?> binding) {
     return visitHasDependencies(binding);
   }
 
-  public Collection<Key<?>> visit(ExposedBinding<?> binding) {
-    // TODO(phopkins): Figure out if this is needed for graphing.
-    return ImmutableSet.of();
-  }
-
-  public Collection<Key<?>> visit(InstanceBinding<?> binding) {
+  @Override public Collection<Key<?>> visit(InstanceBinding<?> binding) {
     return visitHasDependencies(binding);
   }
 
-  public Collection<Key<?>> visit(LinkedKeyBinding<?> binding) {
+  @Override public Collection<Key<?>> visit(LinkedKeyBinding<?> binding) {
     return ImmutableSet.<Key<?>>of(binding.getLinkedKey());
   }
 
-  public Collection<Key<?>> visit(ProviderBinding<?> binding) {
+  @Override public Collection<Key<?>> visit(ProviderBinding<?> binding) {
     return ImmutableSet.<Key<?>>of(binding.getProvidedKey());
   }
 
-  public Collection<Key<?>> visit(ProviderInstanceBinding<?> binding) {
+  @Override public Collection<Key<?>> visit(ProviderInstanceBinding<?> binding) {
     return visitHasDependencies(binding);
   }
 
-  public Collection<Key<?>> visit(ProviderKeyBinding<?> binding) {
+  @Override public Collection<Key<?>> visit(ProviderKeyBinding<?> binding) {
     return ImmutableSet.<Key<?>>of(binding.getProviderKey());
   }
 
-  public Collection<Key<?>> visit(UntargettedBinding<?> binding) {
-    // TODO(phopkins): Figure out if this is needed for graphing.
-    return null;
+  @Override public Collection<Key<?>> visitOther(Binding<?> binding) {
+    return ImmutableSet.of();
   }
 }
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/BindingEdgeFactory.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/BindingEdgeFactory.java
deleted file mode 100644
index 825f3ff..0000000
--- a/extensions/grapher/src/com/google/inject/grapher/graphviz/BindingEdgeFactory.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Copyright (C) 2008 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.graphviz;
-
-import com.google.common.collect.ImmutableList;
-import com.google.inject.Inject;
-import com.google.inject.grapher.BindingEdge;
-
-/**
- * Graphviz-specific implementation of {@link BindingEdge.Factory}. Uses a
- * {@link GraphvizEdgeAdaptor} to delegate to a {@link GraphvizEdge}.
- *
- * @author phopkins@gmail.com (Pete Hopkins)
- */
-public class BindingEdgeFactory implements BindingEdge.Factory<String, BindingEdge<String>> {
-  private final GraphvizRenderer renderer;
-
-  @Inject
-  public BindingEdgeFactory(GraphvizRenderer renderer) {
-    this.renderer = renderer;
-  }
-
-  public BindingEdge<String> newBindingEdge(String fromId, String toId) {
-    GraphvizEdge edge = new GraphvizEdge(fromId, toId);
-
-    renderer.addEdge(edge);
-    return newAdaptor(edge);
-  }
-
-  protected GraphvizEdgeAdaptor newAdaptor(GraphvizEdge edge) {
-    return new GraphvizEdgeAdaptor(edge);
-  }
-
-  /**
-   * Adaptor class that converts {@link BindingEdge} methods to display
-   * operations on a {@link GraphvizEdge}.
-   */
-  protected class GraphvizEdgeAdaptor implements BindingEdge<String> {
-    protected final GraphvizEdge edge;
-
-    public GraphvizEdgeAdaptor(GraphvizEdge edge) {
-      this.edge = edge;
-
-      this.edge.setStyle(EdgeStyle.DASHED);
-    }
-
-    public void setType(BindingEdge.Type type) {
-      switch (type) {
-      case NORMAL:
-        edge.setArrowHead(ImmutableList.of(ArrowType.NORMAL_OPEN));
-        break;
-
-      case PROVIDER:
-        edge.setArrowHead(ImmutableList.of(ArrowType.NORMAL_OPEN, ArrowType.NORMAL_OPEN));
-        break;
-
-      case CONVERTED_CONSTANT:
-        edge.setArrowHead(ImmutableList.of(ArrowType.NORMAL_OPEN, ArrowType.DOT_OPEN));
-        break;
-      }
-    }
-  }
-}
\ No newline at end of file
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/DependencyEdgeFactory.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/DependencyEdgeFactory.java
deleted file mode 100644
index ae9e441..0000000
--- a/extensions/grapher/src/com/google/inject/grapher/graphviz/DependencyEdgeFactory.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * Copyright (C) 2008 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.graphviz;
-
-import com.google.common.collect.ImmutableList;
-import com.google.inject.Inject;
-import com.google.inject.grapher.DependencyEdge;
-import com.google.inject.spi.InjectionPoint;
-
-/**
- * Graphviz-specific implementation of {@link DependencyEdge.Factory}. Uses a
- * {@link GraphvizEdgeAdaptor} to delegate to a {@link GraphvizEdge}.
- *
- * @author phopkins@gmail.com (Pete Hopkins)
- */
-public class DependencyEdgeFactory
-implements DependencyEdge.Factory<String, DependencyEdge<String>> {
-  private final GraphvizRenderer renderer;
-  private final PortIdFactory portIdFactory;
-
-  @Inject
-  public DependencyEdgeFactory(GraphvizRenderer renderer, PortIdFactory portIdFactory) {
-    this.renderer = renderer;
-    this.portIdFactory = portIdFactory;
-  }
-
-  public DependencyEdge<String> newDependencyEdge(String fromId,
-      InjectionPoint fromPoint, String toId) {
-    GraphvizEdge edge = new GraphvizEdge(fromId, toId);
-
-    if (fromPoint == null) {
-      edge.setTailPortId("header");
-    } else {
-      edge.setTailPortId(portIdFactory.getPortId(fromPoint.getMember()));
-    }
-
-    renderer.addEdge(edge);
-    return newAdaptor(edge);
-  }
-
-  protected GraphvizEdgeAdaptor newAdaptor(GraphvizEdge edge) {
-    return new GraphvizEdgeAdaptor(edge);
-  }
-
-  /**
-   * Adaptor class that converts {@link DependencyEdge} methods to display
-   * operations on a {@link GraphvizEdge}.
-   */
-  protected class GraphvizEdgeAdaptor implements DependencyEdge<String> {
-    protected final GraphvizEdge edge;
-
-    public GraphvizEdgeAdaptor(GraphvizEdge edge) {
-      this.edge = edge;
-
-      this.edge.setArrowHead(ImmutableList.of(ArrowType.NORMAL));
-      this.edge.setTailCompassPoint(CompassPoint.EAST);
-    }
-  }
-}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/Graphviz.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/Graphviz.java
new file mode 100644
index 0000000..b86eadf
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/Graphviz.java
@@ -0,0 +1,30 @@
+/**
+ * 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.graphviz;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Annotation for types used by the graphviz grapher.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+@BindingAnnotation
+@Retention(RetentionPolicy.RUNTIME)
+@interface Graphviz {}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizEdge.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizEdge.java
index 9d1b9f0..39fd4bd 100644
--- a/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizEdge.java
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizEdge.java
@@ -17,6 +17,7 @@
 package com.google.inject.grapher.graphviz;
 
 import com.google.common.collect.ImmutableList;
+import com.google.inject.grapher.NodeId;
 
 import java.util.List;
 
@@ -27,24 +28,24 @@
  * @author phopkins@gmail.com (Pete Hopkins)
  */
 public class GraphvizEdge {
-  private final String headNodeId;
+  private final NodeId headNodeId;
   private String headPortId;
   private CompassPoint headCompassPoint;
   private List<ArrowType> arrowHead = ImmutableList.of(ArrowType.NORMAL);
   
-  private final String tailNodeId;
+  private final NodeId tailNodeId;
   private String tailPortId;
   private CompassPoint tailCompassPoint;
   private List<ArrowType> arrowTail = ImmutableList.of(ArrowType.NONE);
 
   private EdgeStyle style = EdgeStyle.SOLID;
 
-  public GraphvizEdge(String tailNodeId, String headNodeId) {
+  public GraphvizEdge(NodeId tailNodeId, NodeId headNodeId) {
     this.tailNodeId = tailNodeId;
     this.headNodeId = headNodeId;
   }
 
-  public String getHeadNodeId() {
+  public NodeId getHeadNodeId() {
     return headNodeId;
   }
 
@@ -72,7 +73,7 @@
     this.arrowHead = ImmutableList.copyOf(arrowHead);
   }
 
-  public String getTailNodeId() {
+  public NodeId getTailNodeId() {
     return tailNodeId;
   }
 
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizGrapher.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizGrapher.java
new file mode 100644
index 0000000..c9daaea
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizGrapher.java
@@ -0,0 +1,325 @@
+/**
+ * Copyright (C) 2008 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.graphviz;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.inject.Inject;
+import com.google.inject.Key;
+import com.google.inject.grapher.AbstractInjectorGrapher;
+import com.google.inject.grapher.BindingEdge;
+import com.google.inject.grapher.DependencyEdge;
+import com.google.inject.grapher.ImplementationNode;
+import com.google.inject.grapher.InstanceNode;
+import com.google.inject.grapher.InterfaceNode;
+import com.google.inject.grapher.NameFactory;
+import com.google.inject.grapher.NodeId;
+import com.google.inject.spi.InjectionPoint;
+import java.io.PrintWriter;
+import java.lang.reflect.Member;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * {@link InjectorGrapher} implementation that writes out a Graphviz DOT file of the graph.
+ * Dependencies are bound in {@link GraphvizModule}.
+ * <p>
+ * Specify the {@link PrintWriter} to output to with {@link #setOut(PrintWriter)}.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class GraphvizGrapher extends AbstractInjectorGrapher {
+  private final Map<NodeId, GraphvizNode> nodes = Maps.newHashMap();
+  private final List<GraphvizEdge> edges = Lists.newArrayList();
+  private final NameFactory nameFactory;
+  private final PortIdFactory portIdFactory;
+
+  private PrintWriter out;
+  private String rankdir = "TB";
+
+  @Inject GraphvizGrapher(@Graphviz NameFactory nameFactory,
+      @Graphviz PortIdFactory portIdFactory) {
+    this.nameFactory = nameFactory;
+    this.portIdFactory = portIdFactory;
+  }
+
+  @Override protected void reset() {
+    nodes.clear();
+    edges.clear();
+  }
+
+  public void setOut(PrintWriter out) {
+    this.out = out;
+  }
+
+  public void setRankdir(String rankdir) {
+    this.rankdir = rankdir;
+  }
+
+  @Override protected void postProcess() {
+    start();
+    
+    for (GraphvizNode node : nodes.values()) {
+      renderNode(node);
+    }
+
+    for (GraphvizEdge edge : edges) {
+      renderEdge(edge);
+    }
+    
+    finish();
+    
+    out.flush();
+  }
+
+  protected Map<String, String> getGraphAttributes() {
+    Map<String, String> attrs = Maps.newHashMap();
+    attrs.put("rankdir", rankdir);
+    return attrs;
+  }
+
+  protected void start() {
+    out.println("digraph injector {");
+    
+    Map<String, String> attrs = getGraphAttributes();
+    out.println("graph " + getAttrString(attrs) + ";");
+  }
+
+  protected void finish() {
+    out.println("}");
+  }
+
+  protected void renderNode(GraphvizNode node) {
+    Map<String, String> attrs = getNodeAttributes(node);
+    out.println(node.getIdentifier() + " " + getAttrString(attrs));
+  }
+  
+  protected Map<String, String> getNodeAttributes(GraphvizNode node) {
+    Map<String, String> attrs = Maps.newHashMap();
+
+    attrs.put("label", getNodeLabel(node));
+    // remove most of the margin because the table has internal padding
+    attrs.put("margin", "0.02,0");
+    attrs.put("shape", node.getShape().toString());
+    attrs.put("style", node.getStyle().toString());
+    
+    return attrs;
+  }
+
+  /**
+   * Creates the "label" for a node. This is a string of HTML that defines a
+   * table with a heading at the top and (in the case of
+   * {@link ImplementationNode}s) rows for each of the member fields.
+   */
+  protected String getNodeLabel(GraphvizNode node) {
+    String cellborder = node.getStyle() == NodeStyle.INVISIBLE ? "1" : "0";
+    
+    StringBuilder html = new StringBuilder();
+    html.append("<");
+    html.append("<table cellspacing=\"0\" cellpadding=\"5\" cellborder=\"");
+    html.append(cellborder).append("\" border=\"0\">");
+    
+    html.append("<tr>").append("<td align=\"left\" port=\"header\" ");
+    html.append("bgcolor=\"" + node.getHeaderBackgroundColor() + "\">");
+    
+    String subtitle = Joiner.on("<br align=\"left\"/>").join(node.getSubtitles());
+    if (subtitle.length() != 0) {
+      html.append("<font color=\"").append(node.getHeaderTextColor());
+      html.append("\" point-size=\"10\">");
+      html.append(subtitle).append("<br align=\"left\"/>").append("</font>");
+    }
+
+    html.append("<font color=\"" + node.getHeaderTextColor() + "\">");
+    html.append(htmlEscape(node.getTitle())).append("<br align=\"left\"/>");
+    html.append("</font>").append("</td>").append("</tr>");
+
+    for (Map.Entry<String, String> field : node.getFields().entrySet()) {
+      html.append("<tr>");
+      html.append("<td align=\"left\" port=\"").append(htmlEscape(field.getKey())).append("\">");
+      html.append(htmlEscape(field.getValue()));
+      html.append("</td>").append("</tr>");
+    }
+
+    html.append("</table>");
+    html.append(">");
+    return html.toString();
+  }
+
+  protected void renderEdge(GraphvizEdge edge) {
+    Map<String, String> attrs = getEdgeAttributes(edge);
+    
+    String tailId = getEdgeEndPoint(nodes.get(edge.getTailNodeId()).getIdentifier(),
+        edge.getTailPortId(), edge.getTailCompassPoint());
+
+    String headId = getEdgeEndPoint(nodes.get(edge.getHeadNodeId()).getIdentifier(),
+        edge.getHeadPortId(), edge.getHeadCompassPoint());
+    
+    out.println(tailId + " -> " + headId + " " + getAttrString(attrs));
+  }
+
+  protected Map<String, String> getEdgeAttributes(GraphvizEdge edge) {
+    Map<String, String> attrs = Maps.newHashMap();
+    
+    attrs.put("arrowhead", getArrowString(edge.getArrowHead()));
+    attrs.put("arrowtail", getArrowString(edge.getArrowTail()));
+    attrs.put("style", edge.getStyle().toString());
+    
+    return attrs;
+  }
+  
+  private String getAttrString(Map<String, String> attrs) {
+    List<String> attrList = Lists.newArrayList();
+    
+    for (Entry<String, String> attr : attrs.entrySet()) {
+      String value = attr.getValue();
+
+      if (value != null) {
+        attrList.add(attr.getKey() + "=" + value);
+      }
+    }
+    
+    return "[" + Joiner.on(", ").join(attrList) + "]";
+  }
+
+  /**
+   * Turns a {@link List} of {@link ArrowType}s into a {@link String} that
+   * represents combining them. With Graphviz, that just means concatenating
+   * them.
+   */
+  protected String getArrowString(List<ArrowType> arrows) {
+    return Joiner.on("").join(arrows);
+  }
+
+  protected String getEdgeEndPoint(String nodeId, String portId, CompassPoint compassPoint) {
+    List<String> portStrings = Lists.newArrayList(nodeId);
+    
+    if (portId != null) {
+      portStrings.add(portId);
+    }
+    
+    if (compassPoint != null) {
+      portStrings.add(compassPoint.toString());
+    }
+    
+    return Joiner.on(":").join(portStrings);
+  }
+
+  protected String htmlEscape(String str) {
+    return str.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
+  }
+
+  protected List<String> htmlEscape(List<String> elements) {
+    List<String> escaped = Lists.newArrayList();
+    for (String element : elements) {
+      escaped.add(htmlEscape(element));
+    }
+    return escaped;
+  }
+
+  @Override protected void newInterfaceNode(InterfaceNode node) {
+    // TODO(phopkins): Show the Module on the graph, which comes from the
+    // class name when source is a StackTraceElement.
+
+    NodeId nodeId = node.getId();
+    GraphvizNode gnode = new GraphvizNode(nodeId);
+    gnode.setStyle(NodeStyle.DASHED);
+    Key<?> key = nodeId.getKey();
+    gnode.setTitle(nameFactory.getClassName(key));
+    gnode.addSubtitle(0, nameFactory.getAnnotationName(key));
+    addNode(gnode);
+  }
+
+  @Override protected void newImplementationNode(ImplementationNode node) {
+    NodeId nodeId = node.getId();
+    GraphvizNode gnode = new GraphvizNode(nodeId);
+    gnode.setStyle(NodeStyle.INVISIBLE);
+
+    gnode.setHeaderBackgroundColor("#000000");
+    gnode.setHeaderTextColor("#ffffff");
+    gnode.setTitle(nameFactory.getClassName(nodeId.getKey()));
+
+    for (Member member : node.getMembers()) {
+      gnode.addField(portIdFactory.getPortId(member), nameFactory.getMemberName(member));
+    }
+
+    addNode(gnode);
+  }
+
+  @Override protected void newInstanceNode(InstanceNode node) {
+    NodeId nodeId = node.getId();
+    GraphvizNode gnode = new GraphvizNode(nodeId);
+    gnode.setStyle(NodeStyle.INVISIBLE);
+
+    gnode.setHeaderBackgroundColor("#000000");
+    gnode.setHeaderTextColor("#ffffff");
+    gnode.setTitle(nameFactory.getClassName(nodeId.getKey()));
+
+    gnode.addSubtitle(0, nameFactory.getSourceName(node.getSource()));
+
+    gnode.setHeaderBackgroundColor("#aaaaaa");
+    gnode.setHeaderTextColor("#ffffff");
+    gnode.setTitle(nameFactory.getInstanceName(node.getInstance()));
+
+    for (Member member : node.getMembers()) {
+      gnode.addField(portIdFactory.getPortId(member), nameFactory.getMemberName(member));
+    }
+
+    addNode(gnode);
+  }
+
+  @Override protected void newDependencyEdge(DependencyEdge edge) {
+    GraphvizEdge gedge = new GraphvizEdge(edge.getFromId(), edge.getToId());
+    InjectionPoint fromPoint = edge.getInjectionPoint();
+    if (fromPoint == null) {
+      gedge.setTailPortId("header");
+    } else {
+      gedge.setTailPortId(portIdFactory.getPortId(fromPoint.getMember()));
+    }
+    gedge.setArrowHead(ImmutableList.of(ArrowType.NORMAL));
+    gedge.setTailCompassPoint(CompassPoint.EAST);
+
+    edges.add(gedge);
+  }
+
+  @Override protected void newBindingEdge(BindingEdge edge) {
+    GraphvizEdge gedge = new GraphvizEdge(edge.getFromId(), edge.getToId());
+    gedge.setStyle(EdgeStyle.DASHED);
+    switch (edge.getType()) {
+      case NORMAL:
+        gedge.setArrowHead(ImmutableList.of(ArrowType.NORMAL_OPEN));
+        break;
+
+      case PROVIDER:
+        gedge.setArrowHead(ImmutableList.of(ArrowType.NORMAL_OPEN, ArrowType.NORMAL_OPEN));
+        break;
+
+      case CONVERTED_CONSTANT:
+        gedge.setArrowHead(ImmutableList.of(ArrowType.NORMAL_OPEN, ArrowType.DOT_OPEN));
+        break;
+    }
+
+    edges.add(gedge);
+  }
+
+  private void addNode(GraphvizNode node) {
+    node.setIdentifier("x" + nodes.size());
+    nodes.put(node.getNodeId(), node);
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizModule.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizModule.java
index 9ae6ffe..db7f3f3 100644
--- a/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizModule.java
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizModule.java
@@ -17,43 +17,21 @@
 package com.google.inject.grapher.graphviz;
 
 import com.google.inject.AbstractModule;
-import com.google.inject.Singleton;
-import com.google.inject.TypeLiteral;
-import com.google.inject.grapher.BindingEdge;
-import com.google.inject.grapher.DependencyEdge;
-import com.google.inject.grapher.ImplementationNode;
-import com.google.inject.grapher.InterfaceNode;
 import com.google.inject.grapher.NameFactory;
-import com.google.inject.grapher.NodeAliasFactory;
-import com.google.inject.grapher.NodeIdFactory;
-import com.google.inject.grapher.Renderer;
 import com.google.inject.grapher.ShortNameFactory;
-import com.google.inject.grapher.StringNodeIdFactory;
 
 /**
- * Module that provides {@link GraphvizRenderer} as the {@link Renderer} and
- * binds the other Graphviz factories.
+ * Module that provides classes needed by {@link GraphvizGrapher}.
  *
  * @author phopkins@gmail.com (Pete Hopkins)
  */
 public class GraphvizModule extends AbstractModule {
-  @Override
-  protected void configure() {
-    bind(Renderer.class).to(GraphvizRenderer.class);
-    bind(new TypeLiteral<NodeAliasFactory<String>>() {}).to(GraphvizRenderer.class);
-    bind(GraphvizRenderer.class).in(Singleton.class);
-
-    bind(NameFactory.class).to(ShortNameFactory.class);
-    bind(new TypeLiteral<NodeIdFactory<String>>() {}).to(StringNodeIdFactory.class);
-    bind(PortIdFactory.class).to(PortIdFactoryImpl.class);
-
-    bind(new TypeLiteral<BindingEdge.Factory<String, BindingEdge<String>>>() {})
-        .to(BindingEdgeFactory.class);
-    bind(new TypeLiteral<DependencyEdge.Factory<String, DependencyEdge<String>>>() {})
-        .to(DependencyEdgeFactory.class);
-    bind(new TypeLiteral<InterfaceNode.Factory<String, InterfaceNode<String>>>() {})
-        .to(InterfaceNodeFactory.class);
-    bind(new TypeLiteral<ImplementationNode.Factory<String, ImplementationNode<String>>>() {})
-        .to(ImplementationNodeFactory.class);
+  @Override protected void configure() {
+    bind(NameFactory.class)
+        .annotatedWith(Graphviz.class)
+        .to(ShortNameFactory.class);
+    bind(PortIdFactory.class)
+        .annotatedWith(Graphviz.class)
+        .to(PortIdFactoryImpl.class);
   }
 }
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizNode.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizNode.java
index 8a55432..ed94c17 100644
--- a/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizNode.java
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizNode.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
+import com.google.inject.grapher.NodeId;
 
 import java.util.List;
 import java.util.Map;
@@ -30,7 +31,7 @@
  * @author phopkins@gmail.com (Pete Hopkins)
  */
 public class GraphvizNode {
-  private final String nodeId;
+  private final NodeId nodeId;
 
   private NodeStyle style = NodeStyle.SOLID;
   private NodeShape shape = NodeShape.BOX;
@@ -41,14 +42,16 @@
   private String headerTextColor = "#000000";
   private String headerBackgroundColor = "#ffffff";
 
+  private String identifier;
+
   /** {@link Map} from port ID to field title */
   private Map<String, String> fields = Maps.newLinkedHashMap();
 
-  public GraphvizNode(String nodeId) {
+  public GraphvizNode(NodeId nodeId) {
     this.nodeId = nodeId;
   }
   
-  public String getNodeId() {
+  public NodeId getNodeId() {
     return nodeId;
   }
 
@@ -107,4 +110,12 @@
   public Map<String, String> getFields() {
     return ImmutableMap.copyOf(fields);
   }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
 }
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizRenderer.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizRenderer.java
deleted file mode 100644
index 1402133..0000000
--- a/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizRenderer.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/**
- * Copyright (C) 2008 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.graphviz;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.inject.grapher.ImplementationNode;
-import com.google.inject.grapher.NodeAliasFactory;
-import com.google.inject.grapher.Renderer;
-
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-/**
- * {@link Renderer} implementation that writes out a Graphviz DOT file of the
- * graph. Bound in {@link GraphvizModule}.
- * <p>
- * Specify the {@link PrintWriter} to output to with
- * {@link #setOut(PrintWriter)}.
- *
- * @author phopkins@gmail.com (Pete Hopkins)
- */
-public class GraphvizRenderer implements Renderer, NodeAliasFactory<String> {
-  private final List<GraphvizNode> nodes = Lists.newArrayList();
-  private final List<GraphvizEdge> edges = Lists.newArrayList();
-  private final Map<String, String> aliases = Maps.newHashMap();
-  
-  private PrintWriter out;
-  private String rankdir = "TB";
-
-  public GraphvizRenderer setOut(PrintWriter out) {
-    this.out = out;
-    return this;
-  }
-
-  public GraphvizRenderer setRankdir(String rankdir) {
-    this.rankdir = rankdir;
-    return this;
-  }
-
-  public void addNode(GraphvizNode node) {
-    nodes.add(node);
-  }
-  
-  public void addEdge(GraphvizEdge edge) {
-    edges.add(edge);
-  }
-
-  public void newAlias(String fromId, String toId) {
-    aliases.put(fromId, toId);
-  }
-
-  protected String resolveAlias(String id) {
-    while (aliases.containsKey(id)) {
-      id = aliases.get(id);
-    }
-    
-    return id;
-  }
-  
-  public void render() {
-    start();
-    
-    for (GraphvizNode node : nodes) {
-      renderNode(node);
-    }
-
-    for (GraphvizEdge edge : edges) {
-      renderEdge(edge);
-    }
-    
-    finish();
-    
-    out.flush();
-  }
-
-  protected Map<String, String> getGraphAttributes() {
-    Map<String, String> attrs = Maps.newHashMap();
-    attrs.put("rankdir", rankdir);
-    return attrs;
-  }
-
-  protected void start() {
-    out.println("digraph injector {");
-    
-    Map<String, String> attrs = getGraphAttributes();
-    out.println("graph " + getAttrString(attrs) + ";");
-  }
-
-  protected void finish() {
-    out.println("}");
-  }
-
-  protected void renderNode(GraphvizNode node) {
-    Map<String, String> attrs = getNodeAttributes(node);
-    out.println(node.getNodeId() + " " + getAttrString(attrs));       
-  }
-  
-  protected Map<String, String> getNodeAttributes(GraphvizNode node) {
-    Map<String, String> attrs = Maps.newHashMap();
-
-    attrs.put("label", getNodeLabel(node));
-    // remove most of the margin because the table has internal padding
-    attrs.put("margin", "0.02,0");
-    attrs.put("shape", node.getShape().toString());
-    attrs.put("style", node.getStyle().toString());
-    
-    return attrs;
-  }
-
-  /**
-   * Creates the "label" for a node. This is a string of HTML that defines a
-   * table with a heading at the top and (in the case of
-   * {@link ImplementationNode}s) rows for each of the member fields.
-   */
-  protected String getNodeLabel(GraphvizNode node) {
-    String cellborder = node.getStyle() == NodeStyle.INVISIBLE ? "1" : "0";
-    
-    StringBuilder html = new StringBuilder();
-    html.append("<");
-    html.append("<table cellspacing=\"0\" cellpadding=\"5\" cellborder=\"");
-    html.append(cellborder).append("\" border=\"0\">");
-    
-    html.append("<tr>").append("<td align=\"left\" port=\"header\" ");
-    html.append("bgcolor=\"" + node.getHeaderBackgroundColor() + "\">");
-    
-    String subtitle = Joiner.on("<br align=\"left\"/>").join(node.getSubtitles());
-    if (subtitle.length() != 0) {
-      html.append("<font color=\"").append(node.getHeaderTextColor());
-      html.append("\" point-size=\"10\">");
-      html.append(subtitle).append("<br align=\"left\"/>").append("</font>");
-    }
-
-    html.append("<font color=\"" + node.getHeaderTextColor() + "\">");
-    html.append(htmlEscape(node.getTitle())).append("<br align=\"left\"/>");
-    html.append("</font>").append("</td>").append("</tr>");
-
-    for (Map.Entry<String, String> field : node.getFields().entrySet()) {
-      html.append("<tr>");
-      html.append("<td align=\"left\" port=\"").append(field.getKey()).append("\">");
-      html.append(htmlEscape(field.getValue()));
-      html.append("</td>").append("</tr>");
-    }
-
-    html.append("</table>");
-    html.append(">");
-    return html.toString();
-  }
-
-  protected void renderEdge(GraphvizEdge edge) {
-    Map<String, String> attrs = getEdgeAttributes(edge);
-    
-    String tailId = getEdgeEndPoint(resolveAlias(edge.getTailNodeId()), edge.getTailPortId(),
-        edge.getTailCompassPoint());
-
-    String headId = getEdgeEndPoint(resolveAlias(edge.getHeadNodeId()), edge.getHeadPortId(),
-        edge.getHeadCompassPoint());
-    
-    out.println(tailId + " -> " + headId + " " + getAttrString(attrs));
-  }
-
-  protected Map<String, String> getEdgeAttributes(GraphvizEdge edge) {
-    Map<String, String> attrs = Maps.newHashMap();
-    
-    attrs.put("arrowhead", getArrowString(edge.getArrowHead()));
-    attrs.put("arrowtail", getArrowString(edge.getArrowTail()));
-    attrs.put("style", edge.getStyle().toString());
-    
-    return attrs;
-  }
-  
-  private String getAttrString(Map<String, String> attrs) {
-    List<String> attrList = Lists.newArrayList();
-    
-    for (Entry<String, String> attr : attrs.entrySet()) {
-      String value = attr.getValue();
-
-      if (value != null) {
-        attrList.add(attr.getKey() + "=" + value);
-      }
-    }
-    
-    return "[" + Joiner.on(", ").join(attrList) + "]";
-  }
-
-  /**
-   * Turns a {@link List} of {@link ArrowType}s into a {@link String} that
-   * represents combining them. With Graphviz, that just means concatenating
-   * them.
-   */
-  protected String getArrowString(List<ArrowType> arrows) {
-    return Joiner.on("").join(arrows);
-  }
-
-  protected String getEdgeEndPoint(String nodeId, String portId, CompassPoint compassPoint) {
-    List<String> portStrings = Lists.newArrayList(nodeId);
-    
-    if (portId != null) {
-      portStrings.add(portId);
-    }
-    
-    if (compassPoint != null) {
-      portStrings.add(compassPoint.toString());
-    }
-    
-    return Joiner.on(":").join(portStrings);
-  }
-
-  protected String htmlEscape(String str) {
-    return str.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
-  }
-}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/ImplementationNodeFactory.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/ImplementationNodeFactory.java
deleted file mode 100644
index 5813f8f..0000000
--- a/extensions/grapher/src/com/google/inject/grapher/graphviz/ImplementationNodeFactory.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/**
- * Copyright (C) 2008 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.graphviz;
-
-import com.google.inject.Inject;
-import com.google.inject.Key;
-import com.google.inject.grapher.ImplementationNode;
-import com.google.inject.grapher.NameFactory;
-import com.google.inject.grapher.graphviz.BindingEdgeFactory.GraphvizEdgeAdaptor;
-
-import java.lang.reflect.Member;
-
-/**
- * Graphviz-specific implementation of {@link ImplementationNode.Factory}. Uses
- * a {@link GraphvizEdgeAdaptor} to delegate to a {@link GraphvizNode}.
- *
- * @author phopkins@gmail.com (Pete Hopkins)
- */
-public class ImplementationNodeFactory
-implements ImplementationNode.Factory<String, ImplementationNode<String>> {
-  private final GraphvizRenderer renderer;
-  private final NameFactory nameFactory;
-  private final PortIdFactory portIdFactory;
-
-  @Inject
-  public ImplementationNodeFactory(GraphvizRenderer renderer, NameFactory nameFactory,
-      PortIdFactory portIdFactory) {
-    this.renderer = renderer;
-    this.nameFactory = nameFactory;
-    this.portIdFactory = portIdFactory;
-  }
-
-  public ImplementationNode<String> newImplementationNode(String nodeId) {
-    GraphvizNode node = new GraphvizNode(nodeId);
-
-    renderer.addNode(node);
-    return newAdaptor(node);
-  }
-
-  protected GraphvizNodeAdaptor newAdaptor(GraphvizNode node) {
-    return new GraphvizNodeAdaptor(node);
-  }
-
-  /**
-   * Adaptor class that converts {@link ImplementationNode} methods to display
-   * operations on a {@link GraphvizNode}.
-   */
-  protected class GraphvizNodeAdaptor implements ImplementationNode<String> {
-    protected final GraphvizNode node;
-
-    public GraphvizNodeAdaptor(GraphvizNode node) {
-      this.node = node;
-
-      this.node.setStyle(NodeStyle.INVISIBLE);
-    }
-
-    public void setClassKey(Key<?> key) {
-      node.setHeaderBackgroundColor("#000000");
-      node.setHeaderTextColor("#ffffff");
-      node.setTitle(nameFactory.getClassName(key));
-    }
-
-    public void setInstance(Object instance) {
-      node.setHeaderBackgroundColor("#aaaaaa");
-      node.setHeaderTextColor("#ffffff");
-      node.setTitle(nameFactory.getInstanceName(instance));
-    }
-
-    public void setSource(Object source) {
-      node.addSubtitle(0, nameFactory.getSourceName(source));
-    }
-
-    public void addMember(Member member) {
-      node.addField(portIdFactory.getPortId(member), nameFactory.getMemberName(member));
-    }
-  }
-}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/InterfaceNodeFactory.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/InterfaceNodeFactory.java
deleted file mode 100644
index 10bb3c1..0000000
--- a/extensions/grapher/src/com/google/inject/grapher/graphviz/InterfaceNodeFactory.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * Copyright (C) 2008 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.graphviz;
-
-import com.google.inject.Inject;
-import com.google.inject.Key;
-import com.google.inject.grapher.InterfaceNode;
-import com.google.inject.grapher.NameFactory;
-
-/**
- * Graphviz-specific implementation of {@link InterfaceNode.Factory}. Uses
- * a {@link GraphvizEdgeAdaptor} to delegate to a {@link GraphvizNode}.
- *
- * @author phopkins@gmail.com (Pete Hopkins)
- */
-public class InterfaceNodeFactory implements InterfaceNode.Factory<String, InterfaceNode<String>> {
-  private final GraphvizRenderer renderer;
-  private final NameFactory nameFactory;
-
-  @Inject
-  public InterfaceNodeFactory(GraphvizRenderer renderer, NameFactory nameFactory) {
-    this.renderer = renderer;
-    this.nameFactory = nameFactory;
-  }
-
-  public InterfaceNode<String> newInterfaceNode(String nodeId) {
-    GraphvizNode node = new GraphvizNode(nodeId);
-
-    renderer.addNode(node);
-    return newAdaptor(node);
-  }
-
-  private GraphvizNodeAdaptor newAdaptor(GraphvizNode node) {
-    return new GraphvizNodeAdaptor(node);
-  }
-
-  /**
-   * Adaptor class that converts {@link InterfaceNode} methods to display
-   * operations on a {@link GraphvizNode}.
-   */
-  protected class GraphvizNodeAdaptor implements InterfaceNode<String> {
-    protected final GraphvizNode node;
-
-    public GraphvizNodeAdaptor(GraphvizNode node) {
-      this.node = node;
-
-      this.node.setStyle(NodeStyle.DASHED);
-    }
-
-    public void setKey(Key<?> key) {
-      String title = nameFactory.getClassName(key);
-      node.setTitle(title);
-      node.addSubtitle(0, nameFactory.getAnnotationName(key));
-    }
-
-    public void setSource(Object source) {
-      // TODO(phopkins): Show the Module on the graph, which comes from the
-      // class name when source is a StackTraceElement.
-    }
-  }
-}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/PortIdFactory.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/PortIdFactory.java
index cfb2fe1..bc5deda 100644
--- a/extensions/grapher/src/com/google/inject/grapher/graphviz/PortIdFactory.java
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/PortIdFactory.java
@@ -16,14 +16,11 @@
 
 package com.google.inject.grapher.graphviz;
 
-import com.google.inject.grapher.StringNodeIdFactory;
-
 import java.lang.reflect.Member;
 
 /**
  * Interface for a service that returns Graphviz port IDs, used for naming the
  * rows in {@link ImplementationNode}-displaying {@link GraphvizNode}s.
- * Implemented by {@link StringNodeIdFactory}.
  *
  * @author phopkins@gmail.com (Pete Hopkins)
  */
diff --git a/extensions/grapher/test/com/google/inject/grapher/AbstractInjectorGrapherTest.java b/extensions/grapher/test/com/google/inject/grapher/AbstractInjectorGrapherTest.java
new file mode 100644
index 0000000..8d90f4c
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/AbstractInjectorGrapherTest.java
@@ -0,0 +1,184 @@
+/**
+ * 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.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.inject.AbstractModule;
+import com.google.inject.BindingAnnotation;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Key;
+import com.google.inject.Provider;
+import com.google.inject.spi.InjectionPoint;
+import com.google.testing.testsize.MediumTest;
+
+import junit.framework.TestCase;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Member;
+import java.util.Set;
+
+/**
+ * Test cases for {@link AbstractInjectorGrapher}. This indirectly tests most classes in this
+ * package.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+@MediumTest
+public class AbstractInjectorGrapherTest extends TestCase {
+  private static final String TEST_STRING = "test";
+
+  private static class FakeGrapher extends AbstractInjectorGrapher {
+    final Set<Node> nodes = Sets.newHashSet();
+    final Set<Edge> edges = Sets.newHashSet();
+
+    @Override protected void reset() {
+      nodes.clear();
+      edges.clear();
+    }
+
+    @Override protected void newInterfaceNode(InterfaceNode node) {
+      assertFalse(nodes.contains(node));
+      nodes.add(node);
+    }
+
+    @Override protected void newImplementationNode(ImplementationNode node) {
+      assertFalse(nodes.contains(node));
+      nodes.add(node);
+    }
+
+    @Override protected void newInstanceNode(InstanceNode node) {
+      assertFalse(nodes.contains(node));
+      nodes.add(node);
+    }
+
+    @Override protected void newDependencyEdge(DependencyEdge edge) {
+      assertFalse(edges.contains(edge));
+      edges.add(edge);
+    }
+
+    @Override protected void newBindingEdge(BindingEdge edge) {
+      assertFalse(edges.contains(edge));
+      edges.add(edge);
+    }
+
+    @Override protected void postProcess() {}
+  }
+
+  private static final class Wrapper<T> {
+    T value;
+  }
+
+  @BindingAnnotation
+  @Retention(RetentionPolicy.RUNTIME)
+  private static @interface Ann {}
+  private static interface IA {}
+  private static class A implements IA {
+    @Inject public A(String str) {}
+  }
+  private static class A2 implements IA {
+    @Inject public A2(Provider<String> strProvider) {}
+  }
+
+  private Node aNode;
+  private Node a2Node;
+  private Node iaNode;
+  private Node iaAnnNode;
+  private Node stringNode;
+  private Node stringInstanceNode;
+
+  private FakeGrapher grapher;
+
+  @Override protected void setUp() throws Exception {
+    super.setUp();
+    grapher = new FakeGrapher();
+    Node.ignoreSourceInComparisons = true;
+    aNode = new ImplementationNode(NodeId.newTypeId(Key.get(A.class)), null,
+        ImmutableList.<Member>of(A.class.getConstructor(String.class)));
+    a2Node = new ImplementationNode(NodeId.newTypeId(Key.get(A2.class)), null,
+        ImmutableList.<Member>of(A2.class.getConstructor(Provider.class)));
+    iaNode = new InterfaceNode(NodeId.newTypeId(Key.get(IA.class)), null);
+    iaAnnNode = new InterfaceNode(NodeId.newTypeId(Key.get(IA.class, Ann.class)), null);
+    stringNode = new InterfaceNode(NodeId.newTypeId(Key.get(String.class)), null);
+    stringInstanceNode = new InstanceNode(NodeId.newInstanceId(Key.get(String.class)), null,
+        TEST_STRING, ImmutableList.<Member>of());
+  }
+
+  public void testLinkedAndInstanceBindings() throws Exception {
+    grapher.graph(Guice.createInjector(new AbstractModule() {
+        @Override protected void configure() {
+          bind(IA.class).to(A.class);
+          bind(IA.class).annotatedWith(Ann.class).to(A.class);
+          bind(String.class).toInstance(TEST_STRING);
+        }
+    }));
+
+    Set<Node> expectedNodes =
+        ImmutableSet.<Node>of(iaNode, iaAnnNode, aNode, stringNode, stringInstanceNode);
+    Set<Edge> expectedEdges = ImmutableSet.<Edge>of(
+        new BindingEdge(iaNode.getId(), aNode.getId(), BindingEdge.Type.NORMAL),
+        new BindingEdge(iaAnnNode.getId(), aNode.getId(), BindingEdge.Type.NORMAL),
+        new BindingEdge(stringNode.getId(), stringInstanceNode.getId(), BindingEdge.Type.NORMAL),
+        new DependencyEdge(aNode.getId(), stringNode.getId(),
+            InjectionPoint.forConstructor(A.class.getConstructor(String.class))));
+    assertEquals(expectedNodes, grapher.nodes);
+    assertEquals(expectedEdges, grapher.edges);
+  }
+
+  public void testProviderBindings() throws Exception {
+    final Wrapper<Provider<A2>> wrapper = new Wrapper<Provider<A2>>();
+    grapher.graph(Guice.createInjector(new AbstractModule() {
+        @Override protected void configure() {
+          wrapper.value = getProvider(A2.class);
+          bind(IA.class).toProvider(wrapper.value);
+          bind(A2.class);
+          bind(String.class).toInstance(TEST_STRING);
+        }
+    }));
+
+    Node a2ProviderNode = new InstanceNode(NodeId.newInstanceId(Key.get(IA.class)), null,
+        wrapper.value, ImmutableList.<Member>of());
+    Set<Node> expectedNodes =
+        ImmutableSet.<Node>of(iaNode, stringNode, a2Node, stringInstanceNode, a2ProviderNode);
+    Set<Edge> expectedEdges = ImmutableSet.<Edge>of(
+        new BindingEdge(stringNode.getId(), stringInstanceNode.getId(), BindingEdge.Type.NORMAL),
+        new BindingEdge(iaNode.getId(), a2ProviderNode.getId(), BindingEdge.Type.PROVIDER),
+        new DependencyEdge(a2Node.getId(), stringNode.getId(),
+            InjectionPoint.forConstructor(A2.class.getConstructor(Provider.class))));
+    assertEquals(expectedNodes, grapher.nodes);
+    assertEquals(expectedEdges, grapher.edges);
+  }
+
+  public void testGraphWithGivenRoot() throws Exception {
+    grapher.graph(Guice.createInjector(new AbstractModule() {
+        @Override protected void configure() {
+          bind(IA.class).to(A.class);
+          bind(IA.class).annotatedWith(Ann.class).to(A.class);
+          bind(String.class).toInstance(TEST_STRING);
+        }
+    }), ImmutableSet.<Key<?>>of(Key.get(String.class)));
+
+    Set<Node> expectedNodes = ImmutableSet.<Node>of(stringNode, stringInstanceNode);
+    Set<Edge> expectedEdges = ImmutableSet.<Edge>of(
+        new BindingEdge(stringNode.getId(), stringInstanceNode.getId(), BindingEdge.Type.NORMAL));
+    assertEquals(expectedNodes, grapher.nodes);
+    assertEquals(expectedEdges, grapher.edges);
+  }
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/AllTests.java b/extensions/grapher/test/com/google/inject/grapher/AllTests.java
index 1617d69..af81398 100644
--- a/extensions/grapher/test/com/google/inject/grapher/AllTests.java
+++ b/extensions/grapher/test/com/google/inject/grapher/AllTests.java
@@ -26,7 +26,7 @@
 
   public static Test suite() {
     TestSuite suite = new TestSuite();
-    suite.addTestSuite(GraphingVisitorTest.class);
+    suite.addTestSuite(AbstractInjectorGrapherTest.class);
     suite.addTestSuite(ShortNameFactoryTest.class);
     suite.addTestSuite(TransitiveDependencyVisitorTest.class);
     return suite;
diff --git a/extensions/grapher/test/com/google/inject/grapher/GraphingVisitorTest.java b/extensions/grapher/test/com/google/inject/grapher/GraphingVisitorTest.java
deleted file mode 100644
index 7deb3fc..0000000
--- a/extensions/grapher/test/com/google/inject/grapher/GraphingVisitorTest.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/**
- * Copyright (C) 2008 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 static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.google.inject.Key;
-import com.google.inject.spi.ConstructorBinding;
-import com.google.inject.spi.Dependency;
-import com.google.inject.spi.HasDependencies;
-import com.google.inject.spi.InjectionPoint;
-import com.google.inject.spi.InstanceBinding;
-
-import junit.framework.TestCase;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Tests for {@link GraphingVisitor}.
- *
- * @author phopkins@gmail.com (Pete Hopkins)
- */
-public class GraphingVisitorTest extends TestCase {
-  private NodeIdFactory<String> nodeIdFactory;
-  private InterfaceNode.Factory<String, InterfaceNode<String>> interfaceNodeFactory;
-  private ImplementationNode.Factory<String, ImplementationNode<String>> implementationNodeFactory;
-  private BindingEdge.Factory<String, BindingEdge<String>> bindingEdgeFactory;
-  private DependencyEdge.Factory<String, DependencyEdge<String>> dependencyEdgeFactory;
-  private NodeAliasFactory<String> nodeAliasFactory;
-
-  private GraphingVisitor<String, InterfaceNode<String>, ImplementationNode<String>,
-      BindingEdge<String>, DependencyEdge<String>> graphingVisitor;
-
-  private List<Object> mocks;
-
-  @SuppressWarnings("unchecked")
-  @Override
-  protected void setUp() throws Exception {
-    super.setUp();
-
-    mocks = Lists.newArrayList();
-
-    nodeIdFactory = new StringNodeIdFactory();
-    interfaceNodeFactory = recordMock(createMock(InterfaceNode.Factory.class));
-    implementationNodeFactory = recordMock(createMock(ImplementationNode.Factory.class));
-    bindingEdgeFactory = recordMock(createMock(BindingEdge.Factory.class));
-    dependencyEdgeFactory = new DependencyEdgeFactory();
-    nodeAliasFactory = recordMock(createMock(NodeAliasFactory.class));
-
-    graphingVisitor = new GraphingVisitor<String, InterfaceNode<String>,
-        ImplementationNode<String>, BindingEdge<String>, DependencyEdge<String>>(
-            nodeIdFactory, interfaceNodeFactory, implementationNodeFactory,
-            bindingEdgeFactory, dependencyEdgeFactory, nodeAliasFactory);
-  }
-
-  private <T> T recordMock(T mock) {
-    mocks.add(mock);
-    return mock;
-  }
-
-  public void testNewDependencies_withInjectionPoints() throws Exception {
-    @SuppressWarnings("unchecked")
-    ImplementationNode<String> node = recordMock(createMock(ImplementationNode.class));
-    
-    node.addMember(Obj.class.getDeclaredField("string"));
-    expectLastCall();
-    node.addMember(Obj.class.getDeclaredField("integer"));
-    expectLastCall();
-    node.addMember(Obj.class.getDeclaredField("bool"));
-    expectLastCall();
-    node.addMember(Obj.class.getDeclaredMethod("setInteger", Integer.class));
-    expectLastCall();
-
-    replayAll();
-
-    Injector injector = Guice.createInjector(new ClassModule());
-    ConstructorBinding<?> binding = (ConstructorBinding<?>) injector.getBinding(Obj.class);
-
-    Collection<DependencyEdge<String>> edges = graphingVisitor.newDependencyEdges("", node,
-        binding.getDependencies());
-
-    assertEquals("There should be four edges, from the three fields plus the method",
-        4, edges.size());
-    
-    verifyAll();
-  }
-
-  public void testNewDependencies_withDependencies() throws Exception {
-    @SuppressWarnings("unchecked")
-    ImplementationNode<String> node = recordMock(createMock(ImplementationNode.class));
-    // No members should be added to the node, since the stated dependencies
-    // have no associated {@link InjectionPoint}s.
-
-    replayAll();
-
-    Injector injector = Guice.createInjector(new ClassModule(), new InstanceModule());
-    InstanceBinding<?> binding = (InstanceBinding<?>) injector.getBinding(Obj.class);
-
-    Collection<DependencyEdge<String>> edges = graphingVisitor.newDependencyEdges("", node,
-        binding.getDependencies());
-
-    assertEquals("One edge should be created, for the one stated Integer dependency",
-        1, edges.size());
-    
-    verifyAll();
-  }
-
-  private void replayAll() {
-    for (Object mock : mocks) {
-      replay(mock);
-    }
-  }
-
-  private void verifyAll() {
-    for (Object mock : mocks) {
-      verify(mock);
-    }
-  }
-
-  private class DependencyEdgeFactory
-  implements DependencyEdge.Factory<String, DependencyEdge<String>> {
-    public DependencyEdge<String> newDependencyEdge(String fromId,
-        InjectionPoint fromPoint, String toId) {
-      @SuppressWarnings("unchecked")
-      DependencyEdge<String> edge = createMock(DependencyEdge.class);
-      return edge;
-    }
-  }
-
-  private static class ClassModule extends AbstractModule {
-    @Override
-    protected void configure() {
-      bind(String.class).toInstance("String");
-      bind(Integer.class).toInstance(Integer.valueOf(8));
-      bind(Boolean.class).toInstance(Boolean.TRUE);
-    }
-  }
-
-  private static class InstanceModule extends AbstractModule {
-    @Override
-    protected void configure() {
-      bind(Obj.class).toInstance(new Obj());
-    }
-  }
-
-  private static class Obj implements HasDependencies {
-    @Inject String string;
-    @Inject Integer integer;
-    @Inject Boolean bool;
-
-    @Inject void setInteger(Integer integer) {}
-    
-    public Set<Dependency<?>> getDependencies() {
-      return ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Integer.class)));
-    }
-  }
-}
diff --git a/extensions/grapher/test/com/google/inject/grapher/InjectorGrapherTest.java b/extensions/grapher/test/com/google/inject/grapher/InjectorGrapherTest.java
deleted file mode 100644
index 2df6b9d..0000000
--- a/extensions/grapher/test/com/google/inject/grapher/InjectorGrapherTest.java
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2011 Google Inc. All Rights Reserved.
-
-package com.google.inject.grapher;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import com.google.inject.AbstractModule;
-import com.google.inject.Binding;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Key;
-import com.google.inject.Provides;
-import com.google.inject.name.Named;
-import com.google.inject.name.Names;
-import com.google.inject.spi.BindingTargetVisitor;
-import com.google.inject.spi.ConstructorBinding;
-import com.google.inject.spi.ConvertedConstantBinding;
-import com.google.inject.spi.ExposedBinding;
-import com.google.inject.spi.InstanceBinding;
-import com.google.inject.spi.LinkedKeyBinding;
-import com.google.inject.spi.ProviderBinding;
-import com.google.inject.spi.ProviderInstanceBinding;
-import com.google.inject.spi.ProviderKeyBinding;
-import com.google.inject.spi.UntargettedBinding;
-
-import junit.framework.TestCase;
-
-import java.util.Collection;
-import java.util.Set;
-
-/**
- * Tests for {@link InjectorGrapher}.
- *
- * @author bojand@google.com (Bojan Djordjevic)
- */
-public class InjectorGrapherTest extends TestCase {
-  private static class FakeGraphingVisitor implements BindingTargetVisitor<Object, Void> {
-    private final Set<Key> keys = Sets.newHashSet();
-
-    public Void visit(InstanceBinding<?> binding) {
-      return record(binding);
-    }
-    public Void visit(ProviderInstanceBinding<?> binding) {
-      return record(binding);
-    }
-    public Void visit(ProviderKeyBinding<?> binding) {
-      return record(binding);
-    }
-    public Void visit(LinkedKeyBinding<?> binding) {
-      return record(binding);
-    }
-    public Void visit(ExposedBinding<?> binding) {
-      return record(binding);
-    }
-    public Void visit(UntargettedBinding<?> binding) {
-      return record(binding);
-    }
-    public Void visit(ConstructorBinding<?> binding) {
-      return record(binding);
-    }
-    public Void visit(ConvertedConstantBinding<?> binding) {
-      return record(binding);
-    }
-    public Void visit(ProviderBinding<?> binding) {
-      return record(binding);
-    }
-
-    public Set<Key> getKeys() {
-      return keys;
-    }
-
-    private Void record(Binding<?> binding) {
-      keys.add(binding.getKey());
-      return null;
-    }
-  }
-
-  private static class A {}
-  private static class B {}
-  private static class C {}
-  private static class D {}
-  private static class E {}
-
-  private static class ClassModule extends AbstractModule {
-    @Override protected void configure() {
-      bind(D.class).toInstance(new D());
-      bind(E.class).toInstance(new E());
-    }
-
-    @Provides A provideA(B b, @Named("test") C c) {
-      return new A();
-    }
-
-    @Provides B provideB(D d, E e) {
-      return new B();
-    }
-
-    @Provides @Named("test") C provideC(D d, E e) {
-      return new C();
-    }
-  }
-
-  private final BindingTargetVisitor<Object, Collection<Key<?>>> keyVisitor =
-      new TransitiveDependencyVisitor();
-  private final Renderer renderer = new Renderer() {
-    public void render() {}
-  };
-
-  private FakeGraphingVisitor graphingVisitor;
-  private Injector injector;
-  private InjectorGrapher grapher;
-
-  @Override
-  protected void setUp() throws Exception {
-    super.setUp();
-
-    graphingVisitor = new FakeGraphingVisitor();
-    injector = Guice.createInjector(new ClassModule());
-    grapher = new InjectorGrapher(keyVisitor, graphingVisitor, renderer);
-  }
-
-  /** Tests making a graph rooted at a {@link Class}. */
-  public void testRootedAtClass() throws Exception {
-    grapher.of(injector)
-        .rootedAt(B.class)
-        .graph();
-    assertEquals(ImmutableSet.<Key<?>>of(
-        Key.get(B.class),
-        Key.get(D.class),
-        Key.get(E.class)), graphingVisitor.getKeys());
-  }
-
-  /** Tests making a graph rooted at a {@link Key}. */
-  public void testRootedAtKey() throws Exception {
-    Key cKey = Key.get(C.class, Names.named("test"));
-    grapher.of(injector)
-        .rootedAt(cKey)
-        .graph();
-    assertEquals(ImmutableSet.of(
-        cKey,
-        Key.get(D.class),
-        Key.get(E.class)), graphingVisitor.getKeys());
-  }
-}
\ No newline at end of file
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/AssistedInjectModule.java b/extensions/grapher/test/com/google/inject/grapher/demo/AssistedInjectModule.java
index 2d81957..95a83c1 100644
--- a/extensions/grapher/test/com/google/inject/grapher/demo/AssistedInjectModule.java
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/AssistedInjectModule.java
@@ -17,7 +17,7 @@
 package com.google.inject.grapher.demo;
 
 import com.google.inject.AbstractModule;
-import com.google.inject.assistedinject.FactoryProvider;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
 
 /**
  * Module to add {@link AssistedInject}-based elements to the demo
@@ -28,7 +28,8 @@
 public class AssistedInjectModule extends AbstractModule {
   @Override
   protected void configure() {
-    bind(DancePartyFactory.class).toProvider(
-        FactoryProvider.newFactory(DancePartyFactory.class, DancePartyImpl.class));
+    install(new FactoryModuleBuilder()
+        .implement(DanceParty.class, DancePartyImpl.class)
+        .build(DancePartyFactory.class));
   }
 }
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/DeLorian.java b/extensions/grapher/test/com/google/inject/grapher/demo/DeLorian.java
index c973407..cf3537e 100644
--- a/extensions/grapher/test/com/google/inject/grapher/demo/DeLorian.java
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/DeLorian.java
@@ -24,7 +24,8 @@
   // between a Provider<T> and just @Injecting T. 
   @Inject @Driver Provider<Person> driver;
   @Inject FluxCapacitor fluxCapacitor;
+  @Inject PrivateTestModule.Exposed exposed;
   
   @Inject
   public void setEnergySource(EnergySource energySource) {}
-}
\ No newline at end of file
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/InjectorGrapherDemo.java b/extensions/grapher/test/com/google/inject/grapher/demo/InjectorGrapherDemo.java
index ce285c2..1e36dd5 100644
--- a/extensions/grapher/test/com/google/inject/grapher/demo/InjectorGrapherDemo.java
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/InjectorGrapherDemo.java
@@ -19,10 +19,8 @@
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
-import com.google.inject.grapher.GrapherModule;
-import com.google.inject.grapher.InjectorGrapher;
+import com.google.inject.grapher.graphviz.GraphvizGrapher;
 import com.google.inject.grapher.graphviz.GraphvizModule;
-import com.google.inject.grapher.graphviz.GraphvizRenderer;
 
 import java.io.File;
 import java.io.PrintWriter;
@@ -38,15 +36,13 @@
   public static void main(String[] args) throws Exception {
     // TODO(phopkins): Switch to Stage.TOOL when issue 297 is fixed.
     Injector demoInjector = Guice.createInjector(Stage.DEVELOPMENT,
-        new BackToTheFutureModule(), new MultibinderModule());
+        new BackToTheFutureModule(), new MultibinderModule(), new PrivateTestModule());
     PrintWriter out = new PrintWriter(new File(args[0]), "UTF-8");
 
-    Injector injector = Guice.createInjector(new GrapherModule(), new GraphvizModule());
-    GraphvizRenderer renderer = injector.getInstance(GraphvizRenderer.class);
-    renderer.setOut(out).setRankdir("TB");
-
-    injector.getInstance(InjectorGrapher.class)
-        .of(demoInjector)
-        .graph();
+    Injector injector = Guice.createInjector(new GraphvizModule());
+    GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class);
+    grapher.setOut(out);
+    grapher.setRankdir("TB");
+    grapher.graph(demoInjector);
   }
 }
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/PrivateTestModule.java b/extensions/grapher/test/com/google/inject/grapher/demo/PrivateTestModule.java
new file mode 100644
index 0000000..7619897
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/PrivateTestModule.java
@@ -0,0 +1,34 @@
+/**
+ * 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.demo;
+
+import com.google.inject.PrivateModule;
+
+/**
+ * Module to test private modules and exposed bindings.
+ *
+ * @author bojand@google.com (Bojan Djordjevic)
+ */
+public class PrivateTestModule extends PrivateModule {
+  interface Exposed {}
+  static class Hidden implements Exposed {}
+
+  @Override protected void configure() {
+    bind(Exposed.class).to(Hidden.class);
+    expose(Exposed.class);
+  }
+}