Adds Graphviz-based grapher extension

Adds grapher extension, an implementation for a Graphviz-based tool to
visualize an Injector. Main class is InjectorGrapher, which is available from
the GrapherModule. The GraphvizModule should also be installed for Graphviz
output.

See com.google.inject.grapher.demo.InjectorGrapherDemo for an example and to
generate a sample graph.

Fix for issue 213.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@752 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/extensions/grapher/build.properties b/extensions/grapher/build.properties
new file mode 100644
index 0000000..980684c
--- /dev/null
+++ b/extensions/grapher/build.properties
@@ -0,0 +1,6 @@
+lib.dir=../../lib
+src.dir=src
+test.dir=test
+build.dir=build
+test.class=com.google.inject.grapher.AllTests
+module=com.google.inject.grapher
diff --git a/extensions/grapher/build.xml b/extensions/grapher/build.xml
new file mode 100644
index 0000000..444fe92
--- /dev/null
+++ b/extensions/grapher/build.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<project name="guice-grapher" basedir="." default="jar">
+
+  <import file="../../common.xml"/>
+  
+  <path id="compile.classpath">
+    <fileset dir="${lib.dir}" includes="*.jar"/>
+    <fileset dir="${lib.dir}/build" includes="*.jar"/>
+    <fileset dir="../../build/dist" includes="*.jar"/>
+  </path>
+
+  <target name="jar" depends="jar.withdeps, manifest" description="Build jar.">
+    <jar destfile="${build.dir}/${ant.project.name}-${version}.jar"
+        manifest="${build.dir}/META-INF/MANIFEST.MF">
+      <zipfileset src="${build.dir}/${ant.project.name}-with-deps.jar"
+          excludes="com/google/inject/internal/**"/>
+    </jar>
+  </target>
+
+</project>
diff --git a/extensions/grapher/grapher.iml b/extensions/grapher/grapher.iml
new file mode 100644
index 0000000..ad94b9f
--- /dev/null
+++ b/extensions/grapher/grapher.iml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="guice" exported="" />
+  </component>
+</module>
+
diff --git a/extensions/grapher/src/com/google/inject/grapher/BindingEdge.java b/extensions/grapher/src/com/google/inject/grapher/BindingEdge.java
new file mode 100644
index 0000000..3ec9e79
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/BindingEdge.java
@@ -0,0 +1,59 @@
+/**
+ * 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;
+
+/**
+ * Interface for an 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> {
+  /**
+   * Classification for what kind of binding this edge represents.
+   */
+  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. */
+    PROVIDER,
+    /** Binding is to the interface for a constant of a different type. */
+    CONVERTED_CONSTANT
+  }
+
+  void setType(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);
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/DependencyEdge.java b/extensions/grapher/src/com/google/inject/grapher/DependencyEdge.java
new file mode 100644
index 0000000..8faf0cc
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/DependencyEdge.java
@@ -0,0 +1,50 @@
+/**
+ * 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.base.Nullable;
+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.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ *
+ * @param <K> The type for node IDs.
+ */
+public interface DependencyEdge<K> {
+  /**
+   * 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.
+   */
+  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, @Nullable InjectionPoint fromPoint, K toId);
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/GrapherModule.java b/extensions/grapher/src/com/google/inject/grapher/GrapherModule.java
new file mode 100644
index 0000000..2b945d2
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/GrapherModule.java
@@ -0,0 +1,51 @@
+/**
+ * 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
new file mode 100644
index 0000000..86531c0
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/GraphingVisitor.java
@@ -0,0 +1,386 @@
+/**
+ * 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.base.Nullable;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.inject.Binding;
+import com.google.inject.Inject;
+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.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.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * {@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,
+      Collection<InjectionPoint> injectionPoints) {
+    M node = implementationNodeFactory.newImplementationNode(getClassNodeId(binding));
+    node.setClassKey(binding.getKey());
+    // we don't set the source here because it's not interesting for classes
+
+    for (InjectionPoint injectionPoint : injectionPoints) {
+      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,
+      Collection<InjectionPoint> injectionPoints) {
+    M node = implementationNodeFactory.newImplementationNode(getInstanceNodeId(binding));
+    node.setSource(binding.getSource());
+    node.setInstance(instance);
+
+    for (InjectionPoint injectionPoint : injectionPoints) {
+      node.addMember(injectionPoint.getMember());
+    }
+
+    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 from the given
+   * {@link ImplementationNode} to the {@link Key}s specified in the
+   * {@link InjectionPoint}s.
+   * <p>
+   * Also adds edges for any {@link Dependency}s passed in that are not covered
+   * in the set of {@link InjectionPoint}s.
+   *
+   * @see #newDependencyEdge(Object, InjectionPoint, Dependency)
+   * 
+   * @param nodeId ID of the node that should be the tail of the
+   *     {@link DependencyEdge}s.
+   * @param injectionPoints {@link Collection} of {@link InjectionPoint}s on
+   *     the class or instance represented by the {@link ImplementationNode}.
+   * @param dependencies {@link Collection} of {@link Dependency}s from the
+   *     {@link Binding}. Some {@link Binding}s may have {@link Dependency}s
+   *     even if they do not have {@link InjectionPoint}s.
+   * @return A {@link Collection} of the {@link DependencyEdge}s that were
+   *     added to the graph.
+   */
+  protected Collection<D> newDependencyEdges(K nodeId, Collection<InjectionPoint> injectionPoints,
+      Collection<Dependency<?>> dependencies) {
+    List<D> edges = Lists.newArrayList();
+
+    // Set to keep track of which of the given Dependencies is not duplicated
+    // by the InjectionPoints.
+    Set<Dependency<?>> remainingDependencies = Sets.newHashSet(dependencies);
+    
+    for (InjectionPoint injectionPoint : injectionPoints) {
+      for (Dependency<?> dependency : injectionPoint.getDependencies()) {
+        D edge = newDependencyEdge(nodeId, injectionPoint, dependency);
+        edges.add(edge);
+        remainingDependencies.remove(dependency);
+      }
+    }
+
+    for (Dependency<?> dependency : remainingDependencies) {
+      D edge = newDependencyEdge(nodeId, null, 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,
+      @Nullable 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)
+   * @see #newDependencyEdges(ImplementationNode, Collection, Collection)
+   */
+  public Void visitConstructor(ConstructorBinding<?> binding) {
+    newClassImplementationNode(binding, binding.getInjectionPoints());
+    newDependencyEdges(getClassNodeId(binding), binding.getInjectionPoints(),
+        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 #visitInstance(InstanceBinding)} as an {@link InterfaceNode}
+   * with a {@link BindingEdge} to the {@link String} instance.
+   * 
+   * @see #newInterfaceNode(Binding)
+   * @see #newBindingEdge(InterfaceNode, Object, BindingEdge.Type)
+   */
+  public Void visitConvertedConstant(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 visitExposed(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 {@link BindingNode} 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(InterfaceNode, Object, BindingEdge.Type)
+   * @see #newInstanceImplementationNode(Binding, Object)
+   * @see #newDependencyEdges(ImplementationNode, Collection, Collection)
+   */
+  public Void visitInstance(InstanceBinding<?> binding) {
+    newInterfaceNode(binding);
+    newBindingEdge(getClassNodeId(binding), getInstanceNodeId(binding),
+        BindingEdge.Type.NORMAL);
+
+    newInstanceImplementationNode(binding, binding.getInstance(), binding.getInjectionPoints());
+    newDependencyEdges(getInstanceNodeId(binding), binding.getInjectionPoints(),
+        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(InterfaceNode, Object, BindingEdge.Type)
+   */
+  public Void visitLinkedKey(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 visitProviderBinding(ProviderBinding<?> binding) {
+    nodeAliasFactory.newAlias(getClassNodeId(binding),
+        idFactory.getClassNodeId(binding.getProvidedKey()));
+
+    return null;
+  }
+
+  /**
+   * Same as {@link #visitInstance(InstanceBinding)}, but the
+   * {@link BindingEdge} is {@link BindingEdge.Type#PROVIDER}.
+   * 
+   * @see #newInterfaceNode(Binding)
+   * @see #newBindingEdge(InterfaceNode, Object, BindingEdge.Type)
+   * @see #newInstanceImplementationNode(Binding, Object)
+   * @see #newDependencyEdges(ImplementationNode, Collection, Collection)
+   */
+  public Void visitProviderInstance(ProviderInstanceBinding<?> binding) {
+    newInterfaceNode(binding);
+    newBindingEdge(getClassNodeId(binding), getInstanceNodeId(binding), BindingEdge.Type.PROVIDER);
+
+    newInstanceImplementationNode(binding, binding.getProviderInstance(),
+        binding.getInjectionPoints());
+    newDependencyEdges(getInstanceNodeId(binding), binding.getInjectionPoints(),
+        binding.getDependencies());
+
+    return null;
+  }
+
+  /**
+   * Same as {@link #visitLinkedKey(LinkedKeyBinding)}, but the
+   * {@link BindingEdge} is {@link BindingEdge.Type#PROVIDER}.
+   * 
+   * @see #newInterfaceNode(Binding)
+   * @see #newBindingEdge(InterfaceNode, Object, BindingEdge.Type)
+   */
+  public Void visitProviderKey(ProviderKeyBinding<?> binding) {
+    newInterfaceNode(binding);
+    newBindingEdge(getClassNodeId(binding), idFactory.getClassNodeId(binding.getProviderKey()),
+        BindingEdge.Type.PROVIDER);
+
+    return null;
+  }
+
+  /**
+   * Currently not displayed on the graph.
+   */
+  public Void visitUntargetted(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
new file mode 100644
index 0000000..1cbd185
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/ImplementationNode.java
@@ -0,0 +1,67 @@
+/**
+ * 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;
+
+import java.lang.reflect.Member;
+
+/**
+ * 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.
+ * 
+ * @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);
+
+  /**
+   * 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);
+
+  void setSource(Object source);
+  void addMember(Member member);
+
+  /**
+   * 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);
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/InjectorGrapher.java b/extensions/grapher/src/com/google/inject/grapher/InjectorGrapher.java
new file mode 100644
index 0000000..71ba829
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/InjectorGrapher.java
@@ -0,0 +1,150 @@
+/**
+ * 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.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...)} to specify an initial set of {@link Class}es to
+ * use, and this will graph their transitive bindings and dependencies.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class InjectorGrapher {  
+  private static final Key<Logger> loggerKey = Key.get(Logger.class);
+
+  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;
+  }
+
+  /**
+   * Sets the {@link Injector} to graph.
+   */
+  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;
+  }
+
+  /**
+   * 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);
+      }
+    }
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/InterfaceNode.java b/extensions/grapher/src/com/google/inject/grapher/InterfaceNode.java
new file mode 100644
index 0000000..0e343e5
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/InterfaceNode.java
@@ -0,0 +1,51 @@
+/**
+ * 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;
+
+/**
+ * Node for an interface class that has been bound to an implementation class
+ * or instance. These nodes are basically defined by a {@link Key}.
+ *
+ * @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);
+
+  /**
+   * 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);
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/NameFactory.java b/extensions/grapher/src/com/google/inject/grapher/NameFactory.java
new file mode 100644
index 0000000..b03ee30
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/NameFactory.java
@@ -0,0 +1,35 @@
+/**
+ * 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;
+
+import java.lang.reflect.Member;
+
+/**
+ * Interface for a service that provides nice {@link String}s that we can
+ * display in the graph for the types that come up in {@link Binding}s.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public interface NameFactory {
+  String getMemberName(Member member);
+  String getClassName(Key<?> key);
+  String getClassName(Object instance);
+  String getAnnotationName(Key<?> key);
+  String getSourceName(Object source);
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/NodeAliasFactory.java b/extensions/grapher/src/com/google/inject/grapher/NodeAliasFactory.java
new file mode 100644
index 0000000..2e4fef1
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/NodeAliasFactory.java
@@ -0,0 +1,31 @@
+/**
+ * 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;
+
+/**
+ * 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)
+ */
+public interface NodeAliasFactory<K> {
+  /**
+   * Makes edges that would point to {@code fromId} point to
+   * {@code toId} instead.
+   */
+  void newAlias(K fromId, K toId);
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/NodeIdFactory.java b/extensions/grapher/src/com/google/inject/grapher/NodeIdFactory.java
new file mode 100644
index 0000000..a61e24b
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/NodeIdFactory.java
@@ -0,0 +1,37 @@
+/**
+ * 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/Renderer.java b/extensions/grapher/src/com/google/inject/grapher/Renderer.java
new file mode 100644
index 0000000..a5e6b77
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/Renderer.java
@@ -0,0 +1,33 @@
+/**
+ * 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/ShortNameFactory.java b/extensions/grapher/src/com/google/inject/grapher/ShortNameFactory.java
new file mode 100644
index 0000000..e50a950
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/ShortNameFactory.java
@@ -0,0 +1,83 @@
+/**
+ * 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.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Reasonable implementation for {@link NameFactory}. Mostly takes various
+ * {@link Object#toString()}s and strips package names out of them so that
+ * they'll fit on the graph.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class ShortNameFactory implements NameFactory {
+  public String getMemberName(Member member) {
+    if (member instanceof Constructor) {
+      return "<init>";
+    } else if (member instanceof Method) {
+      return "#" + member.getName() + "(...)";
+    } else {
+      return member.getName();      
+    }
+  }
+
+  public String getAnnotationName(Key<?> key) {
+    Annotation annotation = key.getAnnotation();
+    Class<? extends Annotation> annotationType = key.getAnnotationType();
+    if (annotation != null) {
+      annotationType = annotation.annotationType();
+
+      String annotationString = annotation.toString();
+      String canonicalName = annotationType.getName();
+      String simpleName = annotationType.getSimpleName();
+ 
+      return annotationString.replace(canonicalName, simpleName).replace("()", "");
+    } else if (annotationType != null) {
+      return "@" + annotationType.getSimpleName();
+    } else {
+      return "";
+    }
+  }
+
+  public String getClassName(Key<?> key) {
+    TypeLiteral<?> typeLiteral = key.getTypeLiteral();
+    return stripPackages(typeLiteral.toString());
+  }
+
+  public String getClassName(Object instance) {
+    return stripPackages(instance.getClass().getName());
+  }
+  
+  public String getSourceName(Object source) {
+    return stripPackages(source.toString());
+  }
+
+  /**
+   * Eliminates runs of lowercase characters and numbers separated by periods.
+   * Seems to remove packages from fully-qualified type names pretty well.
+   */
+  private String stripPackages(String str) {
+    return str.replaceAll("(^|[< .\\(])([a-z0-9]+\\.)*", "$1");
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/StringNodeIdFactory.java b/extensions/grapher/src/com/google/inject/grapher/StringNodeIdFactory.java
new file mode 100644
index 0000000..2b2a115
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/StringNodeIdFactory.java
@@ -0,0 +1,35 @@
+/**
+ * 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
new file mode 100644
index 0000000..f364b80
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/TransitiveDependencyVisitor.java
@@ -0,0 +1,105 @@
+/**
+ * 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.ImmutableSet;
+import com.google.common.collect.Sets;
+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.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.util.Collection;
+import java.util.Set;
+
+/**
+ * {@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 Binding}s.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class TransitiveDependencyVisitor
+implements BindingTargetVisitor<Object, Collection<Key<?>>> {
+
+  // TODO(phopkins): Remove InjectionPoints when issue 298 is fixed.
+  private Collection<Key<?>> visitHasDependencies(HasDependencies hasDependencies,
+      Collection<InjectionPoint> injectionPoints) {
+    Set<Key<?>> dependencies = Sets.newHashSet();
+    
+    for (Dependency<?> dependency : hasDependencies.getDependencies()) {
+      dependencies.add(dependency.getKey());
+    }
+
+    for (InjectionPoint injectionPoint : injectionPoints) {
+      for (Dependency<?> dependency : injectionPoint.getDependencies()) {
+        dependencies.add(dependency.getKey());
+      }
+    }
+
+    return dependencies;
+  }
+  
+  public Collection<Key<?>> visitConstructor(ConstructorBinding<?> binding) {
+    return visitHasDependencies(binding, binding.getInjectionPoints());
+  }
+
+  public Collection<Key<?>> visitConvertedConstant(ConvertedConstantBinding<?> binding) {
+    return visitHasDependencies(binding, ImmutableSet.<InjectionPoint>of());
+  }
+
+  public Collection<Key<?>> visitExposed(ExposedBinding<?> binding) {
+    // TODO(phopkins): Figure out if this is needed for graphing.
+    return ImmutableSet.of();
+  }
+
+  public Collection<Key<?>> visitInstance(InstanceBinding<?> binding) {
+    return visitHasDependencies(binding, binding.getInjectionPoints());
+  }
+
+  public Collection<Key<?>> visitLinkedKey(LinkedKeyBinding<?> binding) {
+    return ImmutableSet.<Key<?>>of(binding.getLinkedKey());
+  }
+
+  public Collection<Key<?>> visitProviderBinding(ProviderBinding<?> binding) {
+    return ImmutableSet.<Key<?>>of(binding.getProvidedKey());
+  }
+
+  public Collection<Key<?>> visitProviderInstance(ProviderInstanceBinding<?> binding) {
+    return visitHasDependencies(binding, binding.getInjectionPoints());
+  }
+
+  public Collection<Key<?>> visitProviderKey(ProviderKeyBinding<?> binding) {
+    return ImmutableSet.<Key<?>>of(binding.getProviderKey());
+  }
+
+  public Collection<Key<?>> visitUntargetted(UntargettedBinding<?> binding) {
+    // TODO(phopkins): Figure out if this is needed for graphing.
+    return null;
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/ArrowType.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/ArrowType.java
new file mode 100644
index 0000000..3f1d807
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/ArrowType.java
@@ -0,0 +1,53 @@
+/**
+ * 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;
+
+/**
+ * Arrow symbols that are available from Graphviz. These can be composed by
+ * concatenation to make double arrows and such.
+ * <p>
+ * See: http://www.graphviz.org/doc/info/arrows.html
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public enum ArrowType {
+  BOX("box"),
+  BOX_OPEN("obox"),
+  CROW("crow"),
+  DIAMOND("diamond"),
+  DIAMOND_OPEN("odiamond"),
+  DOT("dot"),
+  DOT_OPEN("odot"),
+  INVERTED("inv"),
+  INVERTED_OPEN("oinv"),
+  NONE("none"),
+  NORMAL("normal"),
+  NORMAL_OPEN("onormal"),
+  TEE("tee"),
+  VEE("vee");
+
+  private final String arrowType;
+
+  ArrowType(String arrowType) {
+    this.arrowType = arrowType;
+  }
+
+  @Override
+  public String toString() {
+    return arrowType;
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/BindingEdgeFactory.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/BindingEdgeFactory.java
new file mode 100644
index 0000000..825f3ff
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/BindingEdgeFactory.java
@@ -0,0 +1,77 @@
+/**
+ * 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/CompassPoint.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/CompassPoint.java
new file mode 100644
index 0000000..7e6945b
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/CompassPoint.java
@@ -0,0 +1,50 @@
+/**
+ * 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;
+
+/**
+ * Enum for the "compass point" values used to control where edge
+ * end points appear on the graph.
+ * <p>
+ * See: http://www.graphviz.org/doc/info/attrs.html#k:portPos
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public enum CompassPoint {
+  NORTH("n"),
+  NORTH_EAST("ne"),
+  EAST("e"),
+  SOUTH_EAST("se"),
+  SOUTH("s"),
+  SOUTH_WEST("sw"),
+  WEST("w"),
+  NORTH_WEST("nw"),
+  CENTER("c"),
+  EXTERIOR_SIDE("_");
+
+  /** Graphviz "compass_pt" value. */
+  private final String compassPt;
+
+  CompassPoint(String compassPt) {
+    this.compassPt = compassPt;
+  }
+
+  @Override
+  public String toString() {
+    return compassPt;
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/DependencyEdgeFactory.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/DependencyEdgeFactory.java
new file mode 100644
index 0000000..ae9e441
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/DependencyEdgeFactory.java
@@ -0,0 +1,73 @@
+/**
+ * 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/EdgeStyle.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/EdgeStyle.java
new file mode 100644
index 0000000..121dbbf
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/EdgeStyle.java
@@ -0,0 +1,43 @@
+/**
+ * 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;
+
+/**
+ * Styles for edges.
+ * <p>
+ * See: http://www.graphviz.org/doc/info/attrs.html#k:style
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public enum EdgeStyle {
+  BOLD("bold"),
+  DASHED("dashed"),
+  DOTTED("dotted"),
+  INVISIBLE("invis"),
+  SOLID("solid");
+
+  private final String name;
+
+  EdgeStyle(String name) {
+    this.name = name;
+  }
+
+  @Override
+  public String toString() {
+    return name;
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizEdge.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizEdge.java
new file mode 100644
index 0000000..9d1b9f0
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizEdge.java
@@ -0,0 +1,110 @@
+/**
+ * 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 java.util.List;
+
+/**
+ * Data object to encapsulate the attributes of Graphviz edges that we're
+ * interested in drawing.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class GraphvizEdge {
+  private final String headNodeId;
+  private String headPortId;
+  private CompassPoint headCompassPoint;
+  private List<ArrowType> arrowHead = ImmutableList.of(ArrowType.NORMAL);
+  
+  private final String 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) {
+    this.tailNodeId = tailNodeId;
+    this.headNodeId = headNodeId;
+  }
+
+  public String getHeadNodeId() {
+    return headNodeId;
+  }
+
+  public String getHeadPortId() {
+    return headPortId;
+  }
+
+  public void setHeadPortId(String headPortId) {
+    this.headPortId = headPortId;
+  }
+
+  public CompassPoint getHeadCompassPoint() {
+    return headCompassPoint;
+  }
+  
+  public void setHeadCompassPoint(CompassPoint headCompassPoint) {
+    this.headCompassPoint = headCompassPoint;
+  }
+
+  public List<ArrowType> getArrowHead() {
+    return arrowHead;
+  }
+
+  public void setArrowHead(List<ArrowType> arrowHead) {
+    this.arrowHead = ImmutableList.copyOf(arrowHead);
+  }
+
+  public String getTailNodeId() {
+    return tailNodeId;
+  }
+
+  public String getTailPortId() {
+    return tailPortId;
+  }
+
+  public void setTailPortId(String tailPortId) {
+    this.tailPortId = tailPortId;
+  }
+
+  public CompassPoint getTailCompassPoint() {
+    return tailCompassPoint;
+  }
+  
+  public void setTailCompassPoint(CompassPoint tailCompassPoint) {
+    this.tailCompassPoint = tailCompassPoint;
+  }
+
+  public List<ArrowType> getArrowTail() {
+    return arrowTail;
+  }
+
+  public void setArrowTail(List<ArrowType> arrowTail) {
+    this.arrowTail = ImmutableList.copyOf(arrowTail);
+  }
+
+  public EdgeStyle getStyle() {
+    return style;
+  }
+
+  public void setStyle(EdgeStyle style) {
+    this.style = style;
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizModule.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizModule.java
new file mode 100644
index 0000000..9ae6ffe
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizModule.java
@@ -0,0 +1,59 @@
+/**
+ * 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.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.
+ *
+ * @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);
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizNode.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizNode.java
new file mode 100644
index 0000000..8a55432
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizNode.java
@@ -0,0 +1,110 @@
+/**
+ * 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.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Data object to encapsulate the attributes of Graphviz nodes that we're
+ * interested in drawing.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class GraphvizNode {
+  private final String nodeId;
+
+  private NodeStyle style = NodeStyle.SOLID;
+  private NodeShape shape = NodeShape.BOX;
+  
+  private String title = "";
+  private Map<Integer, String> subtitles = Maps.newTreeMap();
+  
+  private String headerTextColor = "#000000";
+  private String headerBackgroundColor = "#ffffff";
+
+  /** {@link Map} from port ID to field title */
+  private Map<String, String> fields = Maps.newLinkedHashMap();
+
+  public GraphvizNode(String nodeId) {
+    this.nodeId = nodeId;
+  }
+  
+  public String getNodeId() {
+    return nodeId;
+  }
+
+  public NodeShape getShape() {
+    return shape;
+  }
+  
+  public void setShape(NodeShape shape) {
+    this.shape = shape;
+  }
+  
+  public NodeStyle getStyle() {
+    return style;
+  }
+
+  public void setStyle(NodeStyle style) {
+    this.style = style;
+  }
+
+  public String getTitle() {
+    return title;
+  }
+
+  public void setTitle(String title) {
+    this.title = title;
+  }
+
+  public List<String> getSubtitles() {
+    return ImmutableList.copyOf(subtitles.values());
+  }
+
+  public void addSubtitle(int position, String subtitle) {
+    this.subtitles.put(position, subtitle);
+  }
+
+  public String getHeaderTextColor() {
+    return headerTextColor;
+  }
+
+  public void setHeaderTextColor(String headerTextColor) {
+    this.headerTextColor = headerTextColor;
+  }
+
+  public String getHeaderBackgroundColor() {
+    return headerBackgroundColor;
+  }
+
+  public void setHeaderBackgroundColor(String headerBackgroundColor) {
+    this.headerBackgroundColor = headerBackgroundColor;
+  }
+
+  public void addField(String portId, String title) {
+    fields.put(portId, title);
+  }
+
+  public Map<String, String> getFields() {
+    return ImmutableMap.copyOf(fields);
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizRenderer.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizRenderer.java
new file mode 100644
index 0000000..b432b34
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/GraphvizRenderer.java
@@ -0,0 +1,229 @@
+/**
+ * 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.Join;
+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 = Join.join("<br align=\"left\"/>", node.getSubtitles());
+    if (subtitle.length() != 0) {
+      html.append("<font align=\"left\" 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 "[" + Join.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 Join.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 Join.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
new file mode 100644
index 0000000..1a92138
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/ImplementationNodeFactory.java
@@ -0,0 +1,91 @@
+/**
+ * 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.getClassName(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
new file mode 100644
index 0000000..10bb3c1
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/InterfaceNodeFactory.java
@@ -0,0 +1,75 @@
+/**
+ * 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/NodeShape.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/NodeShape.java
new file mode 100644
index 0000000..25e4286
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/NodeShape.java
@@ -0,0 +1,41 @@
+/**
+ * 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;
+
+/**
+ * Enum for the shapes that are most interesting for Guice graphing.
+ * <p>
+ * See: http://www.graphviz.org/doc/info/shapes.html
+ * 
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public enum NodeShape {
+  BOX("box"),
+  ELLIPSE("ellipse"),
+  NONE("none");
+  
+  private final String shape;
+  
+  NodeShape(String shape) {
+    this.shape = shape;
+  }
+
+  @Override
+  public String toString() {
+    return shape;
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/NodeStyle.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/NodeStyle.java
new file mode 100644
index 0000000..bd9652d
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/NodeStyle.java
@@ -0,0 +1,46 @@
+/**
+ * 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;
+
+/**
+ * Styles for nodes. Similar to {@link EdgeStyle} but with a few more options.
+ * <p>
+ * See: http://www.graphviz.org/doc/info/attrs.html#k:style
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public enum NodeStyle {
+  BOLD("bold"),
+  DASHED("dashed"),
+  DIAGONALS("diagonals"),
+  DOTTED("dotted"),
+  INVISIBLE("invis"),
+  FILLED("filled"),
+  ROUNDED("rounded"),
+  SOLID("solid");
+
+  private final String name;
+
+  NodeStyle(String name) {
+    this.name = name;
+  }
+
+  @Override
+  public String toString() {
+    return name;
+  }
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/PortIdFactory.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/PortIdFactory.java
new file mode 100644
index 0000000..cfb2fe1
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/PortIdFactory.java
@@ -0,0 +1,32 @@
+/**
+ * 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.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)
+ */
+public interface PortIdFactory {
+  String getPortId(Member member);
+}
diff --git a/extensions/grapher/src/com/google/inject/grapher/graphviz/PortIdFactoryImpl.java b/extensions/grapher/src/com/google/inject/grapher/graphviz/PortIdFactoryImpl.java
new file mode 100644
index 0000000..71b30d6
--- /dev/null
+++ b/extensions/grapher/src/com/google/inject/grapher/graphviz/PortIdFactoryImpl.java
@@ -0,0 +1,30 @@
+/**
+ * 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 java.lang.reflect.Member;
+
+/**
+ * Implementation of {@link PortIdFactory}. Bound in {@link GraphvizModule}.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class PortIdFactoryImpl implements PortIdFactory {
+  public String getPortId(Member member) {
+    return "m_" + Integer.toHexString(member.hashCode());
+  }
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/AllTests.java b/extensions/grapher/test/com/google/inject/grapher/AllTests.java
new file mode 100644
index 0000000..1617d69
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/AllTests.java
@@ -0,0 +1,34 @@
+/**
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class AllTests {
+
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+    suite.addTestSuite(GraphingVisitorTest.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
new file mode 100644
index 0000000..4f3201d
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/GraphingVisitorTest.java
@@ -0,0 +1,156 @@
+/**
+ * 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.replay;
+import static org.easymock.EasyMock.verify;
+
+import com.google.common.collect.ImmutableList;
+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.Provides;
+import com.google.inject.spi.ConstructorBinding;
+import com.google.inject.spi.InjectionPoint;
+import com.google.inject.spi.ProviderInstanceBinding;
+
+import java.util.Collection;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * 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 {
+    replayAll();
+
+    ConstructorBinding<?> binding = (ConstructorBinding<?>) createInjector().getBinding(Obj.class);
+
+    Collection<DependencyEdge<String>> edges = graphingVisitor.newDependencyEdges("",
+        binding.getInjectionPoints(), binding.getDependencies());
+
+    assertEquals("There should be three edges, from the InjectionPoints", 3, edges.size());
+    
+    verifyAll();
+  }
+
+  public void testNewDependencies_withDependencies() throws Exception {
+    replayAll();
+
+    ProviderInstanceBinding<?> binding =
+        (ProviderInstanceBinding<?>) createInjector().getBinding(Intf.class);
+
+    Collection<DependencyEdge<String>> edges = graphingVisitor.newDependencyEdges("",
+        ImmutableList.<InjectionPoint>of(), binding.getDependencies());
+
+    assertEquals("There should be three edges, from the parameter Dependencies", 3, edges.size());
+    
+    verifyAll();
+  }
+
+  private Injector createInjector() {
+    return Guice.createInjector(new TestModule());
+  }
+
+  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 TestModule extends AbstractModule {
+    @Override
+    protected void configure() {
+      bind(String.class).toInstance("String");
+      bind(Integer.class).toInstance(Integer.valueOf(8));
+      bind(Boolean.class).toInstance(Boolean.TRUE);
+    }
+
+    @Provides
+    public Intf provideIntf(String string, Integer integer, Boolean bool) {
+      return null;
+    }
+  }
+
+  private static interface Intf {}
+
+  private static class Obj {
+    @Inject String string;
+    @Inject Integer integer;
+    @Inject Boolean bool;
+  }
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/ShortNameFactoryTest.java b/extensions/grapher/test/com/google/inject/grapher/ShortNameFactoryTest.java
new file mode 100644
index 0000000..556a3c9
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/ShortNameFactoryTest.java
@@ -0,0 +1,109 @@
+/**
+ * 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 java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+import com.google.inject.Key;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.lang.reflect.Member;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link ShortNameFactory}.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class ShortNameFactoryTest extends TestCase {
+  private NameFactory nameFactory;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    nameFactory = new ShortNameFactory();
+  }
+
+  public void testGetMemberName_field() throws Exception {
+    Member field = Obj.class.getDeclaredField("field");
+    assertEquals("field", nameFactory.getMemberName(field));
+  }
+
+  public void testGetMemberName_method() throws Exception {
+    Member method = Obj.class.getDeclaredMethod("method", String.class);
+    assertEquals("#method(...)", nameFactory.getMemberName(method));
+  }
+
+  public void testGetMemberName_constructor() throws Exception {
+    Member constructor = Obj.class.getDeclaredConstructor();
+    assertEquals("<init>", nameFactory.getMemberName(constructor));
+  }
+
+  public void testGetAnnotationName_annotationType() throws Exception {
+    Key<?> key = Key.get(String.class, Annotated.class);
+    assertEquals("@Annotated", nameFactory.getAnnotationName(key));
+  }
+
+  public void testGetAnnotationName_annotationInstance() throws Exception {
+    Key<?> key = Key.get(String.class,
+        Obj.class.getDeclaredField("field").getDeclaredAnnotations()[0]);
+    assertEquals("@Annotated", nameFactory.getAnnotationName(key));
+  }
+
+  public void testGetAnnotationName_annotationInstanceWithParameters() throws Exception {
+    Key<?> key = Key.get(String.class, Names.named("name"));
+    assertEquals("@Named(value=name)", nameFactory.getAnnotationName(key));
+  }
+
+  public void testGetClassName_key() throws Exception {
+    Key<?> key = Key.get(Obj.class);
+    assertEquals("Class name should not have the package",
+        "ShortNameFactoryTest$Obj", nameFactory.getClassName(key));
+  }
+  
+  public void testGetClassName_keyWithTypeParameters() throws Exception {
+    Key<?> key = Key.get(new TypeLiteral<Provider<String>>() {});
+    assertEquals("Class name and type values should not have packages",
+        "Provider<String>", nameFactory.getClassName(key));
+  }
+
+  public void testGetSourceName_method() throws Exception {
+    Member method = Obj.class.getDeclaredMethod("method", String.class);
+    assertEquals("Method name and parameters should not have packages",
+        "void ShortNameFactoryTest$Obj.method(String)", nameFactory.getSourceName(method));
+  }
+  
+  private static class Obj {
+    @Annotated
+    String field;
+    Obj() {}
+    void method(String parameter) {}
+  }
+
+  @Retention(RUNTIME)
+  @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
+  @BindingAnnotation
+  private @interface Annotated {}
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/TransitiveDependencyVisitorTest.java b/extensions/grapher/test/com/google/inject/grapher/TransitiveDependencyVisitorTest.java
new file mode 100644
index 0000000..721e109
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/TransitiveDependencyVisitorTest.java
@@ -0,0 +1,200 @@
+/**
+ * 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.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binding;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+import com.google.inject.grapher.TransitiveDependencyVisitor;
+import com.google.inject.name.Names;
+import com.google.inject.spi.ConstructorBinding;
+import com.google.inject.spi.ConvertedConstantBinding;
+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.Set;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link TransitiveDependencyVisitor}.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class TransitiveDependencyVisitorTest extends TestCase {
+  private TransitiveDependencyVisitor visitor;
+  
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    visitor = new TransitiveDependencyVisitor();
+  }
+
+  public void testVisitConstructor() {
+    Binding<?> binding = getBinding(Key.get(ConstructedClass.class));
+    Collection<Key<?>> dependencies = visitor.visitConstructor((ConstructorBinding<?>) binding);
+    
+    assertDependencies(dependencies, Key.get(A.class), Key.get(B.class), Key.get(C.class),
+        Key.get(D.class));
+  }
+
+  public void testVisitConvertedConstant() {
+    Binding<?> binding = getBinding(Key.get(Integer.class, Names.named("number")),
+        new ConvertedConstantModule());
+    Collection<Key<?>> dependencies = visitor.visitConvertedConstant(
+        (ConvertedConstantBinding<?>) binding);
+    
+    assertDependencies(dependencies, Key.get(String.class, Names.named("number")));
+  }
+
+  public void testVisitInstance() {
+    Binding<?> binding = getBinding(Key.get(ConstructedClass.class), new InstanceModule());
+    Collection<Key<?>> dependencies = visitor.visitInstance(
+        (InstanceBinding<?>) binding);
+    
+    // Dependencies will only be on the field- and method-injected classes.
+    assertDependencies(dependencies, Key.get(A.class), Key.get(D.class));
+  }
+
+  public void testVisitLinkedKey() {
+    Binding<?> binding = getBinding(Key.get(Interface.class), new LinkedKeyModule());
+    Collection<Key<?>> dependencies = visitor.visitLinkedKey((LinkedKeyBinding<?>) binding);
+
+    // Dependency should be to the class this interface is bound to.
+    assertDependencies(dependencies, Key.get(ConstructedClass.class));
+  }
+
+  public void testVisitProviderBinding() {
+    Binding<?> binding = getBinding(Key.get(new TypeLiteral<Provider<ConstructedClass>>() {}));
+    Collection<Key<?>> dependencies = visitor.visitProviderBinding((ProviderBinding<?>) binding);
+    
+    assertDependencies(dependencies, Key.get(ConstructedClass.class));
+  }
+
+  public void testVisitProviderInstance() {
+    Binding<?> binding = getBinding(Key.get(ConstructedClass.class),
+        new ProviderInstanceModule());
+    Collection<Key<?>> dependencies = visitor.visitProviderInstance(
+        (ProviderInstanceBinding<?>) binding);
+    
+    // Dependencies will only be on the field- and method-injected classes.
+    assertDependencies(dependencies, Key.get(E.class), Key.get(F.class), Key.get(G.class));
+  }
+
+  public void testVisitProviderKey() {
+    Binding<?> binding = getBinding(Key.get(ConstructedClass.class), new ProviderKeyModule());
+    Collection<Key<?>> dependencies = visitor.visitProviderKey((ProviderKeyBinding<?>) binding);
+
+    // Dependency should be to the class that provides this one.
+    assertDependencies(dependencies, Key.get(ConstructedClassProvider.class));
+  }
+  
+  private Binding<?> getBinding(Key<?> key, Module... modules) {
+    return Guice.createInjector(modules).getBinding(key);
+  }
+  
+  private void assertDependencies(Collection<Key<?>> dependencies, Key<?>... keys) {
+    assertNotNull("Dependencies should not be null", dependencies);
+    assertEquals("There should be " + keys.length + " dependencies",
+        keys.length, dependencies.size());
+
+    for (Key<?> key : keys) {
+      assertTrue("Dependencies should contain " + key, dependencies.contains(key));
+    }
+  }
+
+  private static class A {}
+  private static class B {}
+  private static class C {}
+  private static class D {}
+  private static class E {}
+  private static class F {}
+  private static class G {}
+  
+  private static interface Interface {}
+  
+  private static class ConstructedClass implements Interface {
+    @Inject A a;
+    ConstructedClass() {}
+    @Inject ConstructedClass(B b, C c) {}
+    @Inject void setD(D d) {}
+  }
+
+  private static class ConstructedClassProvider
+  implements Provider<ConstructedClass>, HasDependencies {
+    @Inject E e;
+    ConstructedClassProvider() {}
+    @Inject ConstructedClassProvider(A a, B b, C c) {}
+    @Inject void setF(F f) {}
+    
+    public ConstructedClass get() {
+      return null;
+    }
+
+    public Set<Dependency<?>> getDependencies() {
+      return ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(G.class)));
+    }
+  }
+  
+  private static class ConvertedConstantModule extends AbstractModule {
+    @Override
+    protected void configure() {
+      bindConstant().annotatedWith(Names.named("number")).to("2008");
+    }
+  }
+
+  private static class InstanceModule extends AbstractModule {
+    @Override
+    protected void configure() {
+      bind(ConstructedClass.class).toInstance(new ConstructedClass());
+    }
+  }
+
+  private static class LinkedKeyModule extends AbstractModule {
+    @Override
+    protected void configure() {
+      bind(Interface.class).to(ConstructedClass.class);
+    }
+  }
+  
+  private static class ProviderInstanceModule extends AbstractModule {
+    @Override
+    protected void configure() {
+      bind(ConstructedClass.class).toProvider(new ConstructedClassProvider());
+    }
+  }
+  
+  private static class ProviderKeyModule extends AbstractModule {
+    @Override
+    protected void configure() {
+      bind(ConstructedClass.class).toProvider(ConstructedClassProvider.class);
+    }
+  }
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/AssistedInjectModule.java b/extensions/grapher/test/com/google/inject/grapher/demo/AssistedInjectModule.java
new file mode 100644
index 0000000..2d81957
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/AssistedInjectModule.java
@@ -0,0 +1,34 @@
+/**
+ * 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.demo;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.assistedinject.FactoryProvider;
+
+/**
+ * Module to add {@link AssistedInject}-based elements to the demo
+ * {@link Injector}.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class AssistedInjectModule extends AbstractModule {
+  @Override
+  protected void configure() {
+    bind(DancePartyFactory.class).toProvider(
+        FactoryProvider.newFactory(DancePartyFactory.class, DancePartyImpl.class));
+  }
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/BackToTheFutureModule.java b/extensions/grapher/test/com/google/inject/grapher/demo/BackToTheFutureModule.java
new file mode 100644
index 0000000..f594169
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/BackToTheFutureModule.java
@@ -0,0 +1,52 @@
+/**
+ * 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.demo;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import com.google.inject.name.Names;
+
+/**
+ * Module that adds a variety of different kinds of {@link Bindings} to be used
+ * to generate a comprehensive sample graph.
+ * 
+ * @see InjectorGrapherDemo
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class BackToTheFutureModule extends AbstractModule {
+  @Override
+  protected void configure() {
+    bind(DeLorian.class);
+
+    bind(EnergySource.class).annotatedWith(Nuclear.class).to(Plutonium.class);
+    bind(EnergySource.class).annotatedWith(Renewable.class).to(Lightning.class);
+
+    bind(Plutonium.class).toProvider(PlutoniumProvider.class);
+    bind(PinballParts.class).annotatedWith(Used.class).toInstance(new PinballParts());
+
+    bind(Person.class).annotatedWith(Driver.class).to(MartyMcFly.class).in(Singleton.class);
+    bind(Person.class).annotatedWith(Inventor.class).to(DocBrown.class).in(Singleton.class);
+    
+    bindConstant().annotatedWith(Names.named("year")).to("1955");
+  }
+  
+  @Provides
+  public FluxCapacitor provideFluxCapacitor(EnergySource energySource) {
+    return null;
+  }
+}
\ No newline at end of file
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/DanceParty.java b/extensions/grapher/test/com/google/inject/grapher/demo/DanceParty.java
new file mode 100644
index 0000000..930cd5f
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/DanceParty.java
@@ -0,0 +1,19 @@
+/**
+ * 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.demo;
+
+interface DanceParty {}
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/DancePartyFactory.java b/extensions/grapher/test/com/google/inject/grapher/demo/DancePartyFactory.java
new file mode 100644
index 0000000..b131566
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/DancePartyFactory.java
@@ -0,0 +1,26 @@
+/**
+ * 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.demo;
+
+/**
+ * Interface to be used with {@link FactoryProvider}.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+interface DancePartyFactory {
+  DanceParty newDanceParty(String thatNewSound);
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/DancePartyImpl.java b/extensions/grapher/test/com/google/inject/grapher/demo/DancePartyImpl.java
new file mode 100644
index 0000000..14185ec
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/DancePartyImpl.java
@@ -0,0 +1,25 @@
+/**
+ * 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.demo;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+class DancePartyImpl implements DanceParty {
+  @Inject
+  public DancePartyImpl(@Assisted String thatNewSound, MartyMcFly guitarist) {}
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/DeLorian.java b/extensions/grapher/test/com/google/inject/grapher/demo/DeLorian.java
new file mode 100644
index 0000000..c973407
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/DeLorian.java
@@ -0,0 +1,30 @@
+/**
+ * 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.demo;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+class DeLorian {
+  // We @Inject a Provider to demonstrate that the graph doesn't differentiate
+  // between a Provider<T> and just @Injecting T. 
+  @Inject @Driver Provider<Person> driver;
+  @Inject FluxCapacitor fluxCapacitor;
+  
+  @Inject
+  public void setEnergySource(EnergySource energySource) {}
+}
\ No newline at end of file
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/DocBrown.java b/extensions/grapher/test/com/google/inject/grapher/demo/DocBrown.java
new file mode 100644
index 0000000..31c9678
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/DocBrown.java
@@ -0,0 +1,24 @@
+/**
+ * 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.demo;
+
+import com.google.inject.Inject;
+
+class DocBrown implements Person {
+  // handy because it introduces a cycle
+  @Inject DeLorian stylishCar;
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/Driver.java b/extensions/grapher/test/com/google/inject/grapher/demo/Driver.java
new file mode 100644
index 0000000..feb619a
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/Driver.java
@@ -0,0 +1,30 @@
+/**
+ * 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.demo;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.google.inject.BindingAnnotation;
+
+@Retention(RUNTIME)
+@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
+@BindingAnnotation
+@interface Driver {}
\ No newline at end of file
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/EnergySource.java b/extensions/grapher/test/com/google/inject/grapher/demo/EnergySource.java
new file mode 100644
index 0000000..47c1f01
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/EnergySource.java
@@ -0,0 +1,22 @@
+/**
+ * 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.demo;
+
+import com.google.inject.ProvidedBy;
+
+@ProvidedBy(EnergySourceProvider.class)
+interface EnergySource {}
\ No newline at end of file
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/EnergySourceProvider.java b/extensions/grapher/test/com/google/inject/grapher/demo/EnergySourceProvider.java
new file mode 100644
index 0000000..7e8f3ca
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/EnergySourceProvider.java
@@ -0,0 +1,32 @@
+/**
+ * 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.demo;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.name.Named;
+
+class EnergySourceProvider implements Provider<EnergySource> {
+  @Inject void setSources(@Nuclear EnergySource nuclear, @Renewable EnergySource renewable) {}
+  
+  // This will demonstrate a ConvertedConstantBinding.
+  @Inject void setYear(@Named("year") int year) {}
+  
+  public EnergySource get() {
+    return null;
+  }
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/FluxCapacitor.java b/extensions/grapher/test/com/google/inject/grapher/demo/FluxCapacitor.java
new file mode 100644
index 0000000..8dce4cd
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/FluxCapacitor.java
@@ -0,0 +1,19 @@
+/**
+ * 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.demo;
+
+class FluxCapacitor {}
\ 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
new file mode 100644
index 0000000..ce285c2
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/InjectorGrapherDemo.java
@@ -0,0 +1,52 @@
+/**
+ * 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.demo;
+
+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.GraphvizModule;
+import com.google.inject.grapher.graphviz.GraphvizRenderer;
+
+import java.io.File;
+import java.io.PrintWriter;
+
+/**
+ * Application that instantiates {@link BackToTheFutureModule} and graphs it,
+ * writing the output to a DOT-formatted file (filename specified on the
+ * command line).
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class InjectorGrapherDemo {
+  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());
+    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();
+  }
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/Inventor.java b/extensions/grapher/test/com/google/inject/grapher/demo/Inventor.java
new file mode 100644
index 0000000..19e34e7
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/Inventor.java
@@ -0,0 +1,30 @@
+/**
+ * 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.demo;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.google.inject.BindingAnnotation;
+
+@Retention(RUNTIME)
+@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
+@BindingAnnotation
+@interface Inventor {}
\ No newline at end of file
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/Lightning.java b/extensions/grapher/test/com/google/inject/grapher/demo/Lightning.java
new file mode 100644
index 0000000..6df5539
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/Lightning.java
@@ -0,0 +1,24 @@
+/**
+ * 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.demo;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
+class Lightning implements EnergySource {
+  @Inject @Named("year") String yearOfStrike;
+}
\ No newline at end of file
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/MartyMcFly.java b/extensions/grapher/test/com/google/inject/grapher/demo/MartyMcFly.java
new file mode 100644
index 0000000..96a8510
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/MartyMcFly.java
@@ -0,0 +1,19 @@
+/**
+ * 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.demo;
+
+class MartyMcFly implements Person {}
\ No newline at end of file
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/MultibinderModule.java b/extensions/grapher/test/com/google/inject/grapher/demo/MultibinderModule.java
new file mode 100644
index 0000000..bf8f7ec
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/MultibinderModule.java
@@ -0,0 +1,34 @@
+/**
+ * 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.demo;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.multibindings.Multibinder;
+
+/**
+ * Module to add {@link Multibinder}-based bindings to the injector.
+ *
+ * @author phopkins@gmail.com (Pete Hopkins)
+ */
+public class MultibinderModule extends AbstractModule {
+  @Override
+  protected void configure() {
+    Multibinder<Person> charactersBinder = Multibinder.newSetBinder(binder(), Person.class);
+    charactersBinder.addBinding().to(MartyMcFly.class);
+    charactersBinder.addBinding().to(DocBrown.class);
+  }
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/Nuclear.java b/extensions/grapher/test/com/google/inject/grapher/demo/Nuclear.java
new file mode 100644
index 0000000..0e4dd38
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/Nuclear.java
@@ -0,0 +1,30 @@
+/**
+ * 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.demo;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.google.inject.BindingAnnotation;
+
+@Retention(RUNTIME)
+@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
+@BindingAnnotation
+@interface Nuclear {}
\ No newline at end of file
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/Person.java b/extensions/grapher/test/com/google/inject/grapher/demo/Person.java
new file mode 100644
index 0000000..60a38dc
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/Person.java
@@ -0,0 +1,19 @@
+/**
+ * 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.demo;
+
+interface Person {}
\ No newline at end of file
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/PinballParts.java b/extensions/grapher/test/com/google/inject/grapher/demo/PinballParts.java
new file mode 100644
index 0000000..6ac1104
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/PinballParts.java
@@ -0,0 +1,19 @@
+/**
+ * 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.demo;
+
+class PinballParts {}
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/Plutonium.java b/extensions/grapher/test/com/google/inject/grapher/demo/Plutonium.java
new file mode 100644
index 0000000..6594324
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/Plutonium.java
@@ -0,0 +1,19 @@
+/**
+ * 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.demo;
+
+class Plutonium implements EnergySource {}
\ No newline at end of file
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/PlutoniumProvider.java b/extensions/grapher/test/com/google/inject/grapher/demo/PlutoniumProvider.java
new file mode 100644
index 0000000..3b8600a
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/PlutoniumProvider.java
@@ -0,0 +1,28 @@
+/**
+ * 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.demo;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+class PlutoniumProvider implements Provider<Plutonium> {
+  @Inject public PlutoniumProvider(@Inventor Person inventor, @Used PinballParts parts) {}
+
+  public Plutonium get() {
+    return null;
+  }
+}
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/Renewable.java b/extensions/grapher/test/com/google/inject/grapher/demo/Renewable.java
new file mode 100644
index 0000000..08e2c42
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/Renewable.java
@@ -0,0 +1,30 @@
+/**
+ * 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.demo;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.google.inject.BindingAnnotation;
+
+@Retention(RUNTIME)
+@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
+@BindingAnnotation
+@interface Renewable {}
\ No newline at end of file
diff --git a/extensions/grapher/test/com/google/inject/grapher/demo/Used.java b/extensions/grapher/test/com/google/inject/grapher/demo/Used.java
new file mode 100644
index 0000000..4b973cf
--- /dev/null
+++ b/extensions/grapher/test/com/google/inject/grapher/demo/Used.java
@@ -0,0 +1,30 @@
+/**
+ * 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.demo;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.google.inject.BindingAnnotation;
+
+@Retention(RUNTIME)
+@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
+@BindingAnnotation
+@interface Used {}
\ No newline at end of file