Grand unification of commands and SPI:
 - Command is now Element
 - Binding is an Element
 - Message is an Element
 - All other commands are Elements
 - CommandRecorder is now Elements.getElements(Module)

All the Binding subinterfaces are now just visitor targets in Binding.TargetVisitor.

An old copy of the SPI is available in 'spi.oldversion'. This will be removed soon! But it should make migration possible.

An old copy of Commands is not available in this commit. I'm going to add that back in, to make it migration possible.

Still outstanding:
 - Restore a copy of commands to ease migration. This will get killed before 2.0.
 - Kill the oldversion SPI before 2.0
 - Thorough Javadoc
 - Merge BindConstant and Binding
 - Integrate InjectionPoints with Binding

git-svn-id: https://google-guice.googlecode.com/svn/trunk@567 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/extensions/commands/src/com/google/inject/commands/intercepting/InterceptingInjectorBuilder.java b/extensions/commands/src/com/google/inject/commands/intercepting/InterceptingInjectorBuilder.java
index 3f4465b..3026839 100644
--- a/extensions/commands/src/com/google/inject/commands/intercepting/InterceptingInjectorBuilder.java
+++ b/extensions/commands/src/com/google/inject/commands/intercepting/InterceptingInjectorBuilder.java
@@ -16,10 +16,12 @@
 
 package com.google.inject.commands.intercepting;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import com.google.common.collect.Sets;
 import com.google.inject.AbstractModule;
 import com.google.inject.Binder;
+import com.google.inject.Binding;
 import com.google.inject.Guice;
 import com.google.inject.ImplementedBy;
 import com.google.inject.Inject;
@@ -31,19 +33,16 @@
 import com.google.inject.TypeLiteral;
 import com.google.inject.binder.LinkedBindingBuilder;
 import com.google.inject.binder.ScopedBindingBuilder;
-import com.google.inject.commands.BindCommand;
-import com.google.inject.commands.BindScoping;
-import com.google.inject.commands.BindTarget;
-import com.google.inject.commands.Command;
-import com.google.inject.commands.CommandRecorder;
-import com.google.inject.commands.CommandReplayer;
 import com.google.inject.internal.UniqueAnnotations;
 import com.google.inject.name.Names;
+import com.google.inject.spi.DefaultBindTargetVisitor;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.Elements;
+import com.google.inject.spi.ModuleWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -110,9 +109,8 @@
   }
 
   public InterceptingInjectorBuilder intercept(Collection<Key<?>> keys) {
-    if (keys.contains(INJECTION_INTERCEPTOR_KEY)) {
-      throw new IllegalArgumentException("Cannot intercept the interceptor!");
-    }
+    checkArgument(!keys.contains(INJECTION_INTERCEPTOR_KEY),
+        "Cannot intercept the interceptor!");
 
     keysToIntercept.addAll(keys);
     return this;
@@ -134,11 +132,11 @@
 
   public Injector build() {
     // record commands from the modules
-    List<Command> commands = new CommandRecorder().recordCommands(modules);
+    List<Element> elements = Elements.getElements(modules);
 
     // rewrite the commands to insert interception
-    CommandRewriter rewriter = new CommandRewriter();
-    Module module = rewriter.createModule(commands);
+    ModuleRewriter rewriter = new ModuleRewriter();
+    Module module = rewriter.create(elements);
 
     // create and injector with the rewritten commands
     Injector injector = Guice.createInjector(module);
@@ -155,21 +153,19 @@
     return injector;
   }
 
-  /**
-   * Replays commands, inserting the InterceptingProvider where necessary.
-   */
-  private class CommandRewriter extends CommandReplayer {
-    private Set<Key> keysIntercepted = new HashSet<Key>();
+  /** Replays commands, inserting the InterceptingProvider where necessary. */
+  private class ModuleRewriter extends ModuleWriter {
+    private Set<Key<?>> keysIntercepted = Sets.newHashSet();
 
-    @Override public <T> void replayBind(Binder binder, BindCommand<T> command) {
-      final Key<T> key = command.getKey();
+    @Override public <T> void writeBind(Binder binder, Binding<T> binding) {
+      final Key<T> key = binding.getKey();
 
       if (!keysToIntercept.contains(key)) {
-        super.replayBind(binder, command);
+        super.writeBind(binder, binding);
         return;
       }
 
-      command.getTarget().acceptVisitor(new NoOpBindTargetVisitor<T, Void>() {
+      binding.acceptTargetVisitor(new DefaultBindTargetVisitor<T, Void>() {
         @Override public Void visitUntargetted() {
           throw new UnsupportedOperationException(
               String.format("Cannot intercept bare binding of %s.", key));
@@ -180,15 +176,12 @@
       binder.bind(key).toProvider(new InterceptingProvider<T>(key, anonymousKey));
 
       LinkedBindingBuilder<T> linkedBindingBuilder = binder.bind(anonymousKey);
-      ScopedBindingBuilder scopedBindingBuilder = command.getTarget().execute(linkedBindingBuilder);
+      ScopedBindingBuilder scopedBindingBuilder = applyTarget(binding, linkedBindingBuilder);
 
       // we scope the user's provider, not the interceptor. This is dangerous,
       // but convenient. It means that although the user's provider will live
       // in its proper scope, the intereptor gets invoked without a scope
-      BindScoping scoping = command.getScoping();
-      if (scoping != null) {
-        scoping.execute(scopedBindingBuilder);
-      }
+      applyScoping(binding, scopedBindingBuilder);
 
       keysIntercepted.add(key);
     }
@@ -222,26 +215,4 @@
       return injectionInterceptorProvider.get().intercept(key, delegateProvider);
     }
   }
-
-  private static class NoOpBindTargetVisitor<T, V> implements BindTarget.Visitor<T, V> {
-    public V visitToInstance(T instance) {
-      return null;
-    }
-
-    public V visitToProvider(Provider<? extends T> provider) {
-      return null;
-    }
-
-    public V visitToProviderKey(Key<? extends Provider<? extends T>> providerKey) {
-      return null;
-    }
-
-    public V visitToKey(Key<? extends T> key) {
-      return null;
-    }
-
-    public V visitUntargetted() {
-      return null;
-    }
-  }
 }
diff --git a/src/com/google/inject/AbstractModule.java b/src/com/google/inject/AbstractModule.java
index cebc188..e1f4884 100644
--- a/src/com/google/inject/AbstractModule.java
+++ b/src/com/google/inject/AbstractModule.java
@@ -178,7 +178,7 @@
 
   /**
    * Adds a dependency from this module to {@code type}. When the injector is
-   * created, Guice will report an error if {@code key} cannot be injected.
+   * created, Guice will report an error if {@code type} cannot be injected.
    * Note that this requirement may be satisfied by implicit binding, such as
    * a public no-arguments constructor.
    */
diff --git a/src/com/google/inject/BindElementProcessor.java b/src/com/google/inject/BindElementProcessor.java
new file mode 100644
index 0000000..489e987
--- /dev/null
+++ b/src/com/google/inject/BindElementProcessor.java
@@ -0,0 +1,338 @@
+/**
+ * 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;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.inject.internal.Annotations;
+import com.google.inject.internal.Classes;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.ErrorsException;
+import com.google.inject.internal.StackTraceElements;
+import com.google.inject.spi.BindConstant;
+import com.google.inject.spi.DefaultBindTargetVisitor;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Handles {@link Binder#bind} and {@link Binder#bindConstant} elements.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+class BindElementProcessor extends ElementProcessor {
+
+  private static final com.google.inject.Binding.ScopingVisitor<LoadStrategy> LOAD_STRATEGY_VISITOR
+      = new com.google.inject.Binding.ScopingVisitor<LoadStrategy>() {
+    public LoadStrategy visitEagerSingleton() {
+      return LoadStrategy.EAGER;
+    }
+
+    public LoadStrategy visitScope(Scope scope) {
+      return LoadStrategy.LAZY;
+    }
+
+    public LoadStrategy visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
+      return LoadStrategy.LAZY;
+    }
+
+    public LoadStrategy visitNoScoping() {
+      return LoadStrategy.LAZY;
+    }
+  };
+
+  private final InjectorImpl injector;
+  private final Map<Class<? extends Annotation>, Scope> scopes;
+  private final List<CreationListener> creationListeners = Lists.newArrayList();
+  private final Map<Key<?>, BindingImpl<?>> bindings;
+  private final CreationTimeMemberInjector memberInjector;
+  private final List<Runnable> untargettedBindings = Lists.newArrayList();
+
+  BindElementProcessor(Errors errors,
+      InjectorImpl injector,
+      Map<Class<? extends Annotation>, Scope> scopes,
+      Map<Key<?>, BindingImpl<?>> bindings,
+      CreationTimeMemberInjector memberInjector) {
+    super(errors);
+    this.injector = injector;
+    this.scopes = scopes;
+    this.bindings = bindings;
+    this.memberInjector = memberInjector;
+  }
+
+  @Override public <T> Boolean visitBinding(Binding<T> command) {
+    final Object source = command.getSource();
+
+    final Key<T> key = command.getKey();
+    Class<? super T> rawType = key.getTypeLiteral().getRawType();
+
+    if (rawType == Provider.class) {
+      errors.bindingToProvider();
+      return true;
+    }
+
+    validateKey(command.getSource(), command.getKey());
+
+    final LoadStrategy loadStrategy = command.acceptScopingVisitor(LOAD_STRATEGY_VISITOR);
+    final Scope scope = command.acceptScopingVisitor(new com.google.inject.Binding.ScopingVisitor<Scope>() {
+      public Scope visitEagerSingleton() {
+        return Scopes.SINGLETON;
+      }
+
+      public Scope visitScope(Scope scope) {
+        return scope;
+      }
+
+      public Scope visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
+        Scope scope = scopes.get(scopeAnnotation);
+        if (scope != null) {
+          return scope;
+        } else {
+          errors.scopeNotFound(scopeAnnotation);
+          return null;
+        }
+      }
+
+      public Scope visitNoScoping() {
+        return null;
+      }
+    });
+
+    command.acceptTargetVisitor(new com.google.inject.Binding.TargetVisitor<T, Void>() {
+      public Void visitToInstance(T instance) {
+        if (instance == null) {
+          errors.cannotBindToNullInstance();
+          putBinding(invalidBinding(injector, key, source));
+          return null;
+        }
+
+        ConstantFactory<? extends T> factory = new ConstantFactory<T>(instance);
+        memberInjector.requestInjection(instance, source);
+        InternalFactory<? extends T> scopedFactory
+            = Scopes.scope(key, injector, factory, scope);
+        putBinding(new InstanceBindingImpl<T>(injector, key, source, scopedFactory, instance));
+        return null;
+      }
+
+      public Void visitToProvider(Provider<? extends T> provider) {
+        InternalFactoryToProviderAdapter<? extends T> factory
+            = new InternalFactoryToProviderAdapter<T>(provider, source);
+        memberInjector.requestInjection(provider, source);
+        InternalFactory<? extends T> scopedFactory
+            = Scopes.scope(key, injector, factory, scope);
+        putBinding(new ProviderInstanceBindingImpl<T>(
+                injector, key, source, scopedFactory, scope, provider, loadStrategy));
+        return null;
+      }
+
+      public Void visitToProviderKey(Key<? extends Provider<? extends T>> providerKey) {
+        final BoundProviderFactory<T> boundProviderFactory =
+            new BoundProviderFactory<T>(providerKey, source);
+        creationListeners.add(boundProviderFactory);
+        InternalFactory<? extends T> scopedFactory = Scopes.scope(
+            key, injector, (InternalFactory<? extends T>) boundProviderFactory, scope);
+        putBinding(new LinkedProviderBindingImpl<T>(
+                injector, key, source, scopedFactory, scope, providerKey, loadStrategy));
+        return null;
+      }
+
+      public Void visitToKey(Key<? extends T> targetKey) {
+        if (key.equals(targetKey)) {
+          errors.recursiveBinding();
+        }
+
+        FactoryProxy<T> factory = new FactoryProxy<T>(key, targetKey, source);
+        creationListeners.add(factory);
+        InternalFactory<? extends T> scopedFactory
+            = Scopes.scope(key, injector, factory, scope);
+        putBinding(new LinkedBindingImpl<T>(
+                injector, key, source, scopedFactory, scope, targetKey, loadStrategy));
+        return null;
+      }
+
+      public Void visitUntargetted() {
+        final Type type = key.getTypeLiteral().getType();
+
+        // Error: Missing implementation.
+        // Example: bind(Date.class).annotatedWith(Red.class);
+        // We can't assume abstract types aren't injectable. They may have an
+        // @ImplementedBy annotation or something.
+        if (key.hasAnnotationType() || !(type instanceof Class<?>)) {
+          errors.missingImplementation(key);
+          putBinding(invalidBinding(injector, key, source));
+          return null;
+        }
+
+        // This cast is safe after the preceeding check.
+        @SuppressWarnings("unchecked")
+        Class<T> clazz = (Class<T>) type;
+        final BindingImpl<T> binding;
+        try {
+          binding = injector.createUnitializedBinding(
+              key, clazz, scope, source, loadStrategy, errors);
+          putBinding(binding);
+        } catch (ErrorsException e) {
+          errors.merge(e.getErrors());
+          putBinding(invalidBinding(injector, key, source));
+          return null;
+        }
+
+        untargettedBindings.add(new Runnable() {
+          public void run() {
+            try {
+              injector.initializeBinding(binding, errors.withSource(source));
+            } catch (ErrorsException e) {
+              errors.merge(e.getErrors());
+            }
+          }
+        });
+
+        return null;
+      }
+
+      public Void visitConstant(T value) {
+        throw new IllegalArgumentException("Cannot apply a non-module element");
+      }
+
+      public Void visitConvertedConstant(T value) {
+        throw new IllegalArgumentException("Cannot apply a non-module element");
+      }
+
+      public Void visitConstructor(Constructor<? extends T> constructor) {
+        throw new IllegalArgumentException("Cannot apply a non-module element");
+      }
+
+      public Void visitProviderBinding(Key<?> provided) {
+        throw new IllegalArgumentException("Cannot apply a non-module element");
+      }
+    });
+
+    return true;
+  }
+
+  private <T> void validateKey(Object source, Key<T> key) {
+    if (key.hasAnnotationType()) {
+      Class<? extends Annotation> annotationType = key.getAnnotationType();
+
+      if (!Annotations.isRetainedAtRuntime(annotationType)) {
+        errors.withSource(StackTraceElements.forType(annotationType)).missingRuntimeRetention(source);
+      }
+
+      if (!Key.isBindingAnnotation(annotationType)) {
+        errors.withSource(StackTraceElements.forType(annotationType)).missingBindingAnnotation(source);
+      }
+    }
+
+    Class<? super T> rawType = key.getRawType();
+    if (!Classes.isConcrete(rawType)) {
+      Class<? extends Annotation> scopeAnnotation = Scopes.findScopeAnnotation(errors, rawType);
+      if (scopeAnnotation != null) {
+        errors.withSource(StackTraceElements.forType(rawType))
+            .scopeAnnotationOnAbstractType(scopeAnnotation, rawType, source);
+      }
+    }
+  }
+
+  <T> InvalidBindingImpl<T> invalidBinding(InjectorImpl injector, Key<T> key, Object source) {
+    return new InvalidBindingImpl<T>(injector, key, source);
+  }
+
+  @Override public Boolean visitBindConstant(BindConstant command) {
+    Object value = command.acceptTargetVisitor(new DefaultBindTargetVisitor<Object, Object>() {
+      @Override public Object visitToInstance(Object instance) {
+        return instance;
+      }
+
+      @Override protected Object visitTarget() {
+        errors.missingConstantValues();
+        return null;
+      }
+    });
+
+    if (value == null) {
+      return true;
+    }
+
+    validateKey(command.getSource(), command.getKey());
+    ConstantFactory<Object> factory = new ConstantFactory<Object>(value);
+    putBinding(new ConstantBindingImpl<Object>(
+        injector, command.getKey(), command.getSource(), factory, value));
+
+    return true;
+  }
+
+  public void createUntargettedBindings() {
+    for (Runnable untargettedBinding : untargettedBindings) {
+      untargettedBinding.run();
+    }
+  }
+
+  public void runCreationListeners(InjectorImpl injector) {
+    for (CreationListener creationListener : creationListeners) {
+      creationListener.notify(injector, errors);
+    }
+  }
+
+  private void putBinding(BindingImpl<?> binding) {
+    Key<?> key = binding.getKey();
+    Binding<?> original = bindings.get(key);
+
+    Class<?> rawType = key.getRawType();
+    if (FORBIDDEN_TYPES.contains(rawType)) {
+      errors.cannotBindToGuiceType(rawType.getSimpleName());
+      return;
+    }
+
+    if (bindings.containsKey(key)) {
+      errors.bindingAlreadySet(key, original.getSource());
+    } else {
+      bindings.put(key, binding);
+    }
+  }
+
+  private static Set<Class<?>> FORBIDDEN_TYPES = forbiddenTypes();
+
+  @SuppressWarnings("unchecked") // For generic array creation.
+  private static Set<Class<?>> forbiddenTypes() {
+    Set<Class<?>> set = Sets.newHashSet();
+
+    Collections.addAll(set,
+
+        // It's unfortunate that we have to maintain a blacklist of specific
+        // classes, but we can't easily block the whole package because of
+        // all our unit tests.
+
+        AbstractModule.class,
+        Binder.class,
+        Binding.class,
+        Key.class,
+        Module.class,
+        Provider.class,
+        Scope.class,
+        TypeLiteral.class);
+    return Collections.unmodifiableSet(set);
+  }
+
+  interface CreationListener {
+    void notify(InjectorImpl injector, Errors errors);
+  }
+}
diff --git a/src/com/google/inject/BindInterceptorElementProcessor.java b/src/com/google/inject/BindInterceptorElementProcessor.java
new file mode 100644
index 0000000..c6004bd
--- /dev/null
+++ b/src/com/google/inject/BindInterceptorElementProcessor.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;
+
+import com.google.inject.internal.Errors;
+import com.google.inject.spi.BindInterceptor;
+
+/**
+ * Handles {@link Binder#bindInterceptor} commands.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+class BindInterceptorElementProcessor extends ElementProcessor {
+
+  private final ProxyFactoryBuilder proxyFactoryBuilder;
+
+  BindInterceptorElementProcessor(Errors errors) {
+    super(errors);
+    proxyFactoryBuilder = new ProxyFactoryBuilder();
+  }
+
+  @Override public Boolean visitBindInterceptor(BindInterceptor command) {
+    proxyFactoryBuilder.intercept(
+        command.getClassMatcher(), command.getMethodMatcher(), command.getInterceptors());
+    return true;
+  }
+
+  ProxyFactory createProxyFactory() {
+    return proxyFactoryBuilder.create();
+  }
+}
diff --git a/src/com/google/inject/Binding.java b/src/com/google/inject/Binding.java
index d4a21d8..bb8b05f 100644
--- a/src/com/google/inject/Binding.java
+++ b/src/com/google/inject/Binding.java
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2006 Google Inc.
+ * 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.
@@ -16,8 +16,9 @@
 
 package com.google.inject;
 
-import com.google.inject.spi.BindingVisitor;
-import com.google.inject.spi.ProviderBinding;
+import com.google.inject.spi.Element;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
 
 /**
  * A mapping from a key (type and optional annotation) to a provider of
@@ -25,8 +26,9 @@
  * introspection API and is intended primary for use by tools.
  *
  * @author crazybob@google.com (Bob Lee)
+ * @author jessewilson@google.com (Jesse Wilson)
  */
-public interface Binding<T> {
+public interface Binding<T> extends Element {
 
   /**
    * Returns the key for this binding.
@@ -47,24 +49,39 @@
   /**
    * Returns the scoped provider guice uses to fulfill requests for this
    * binding.
+   *
+   * @throws UnsupportedOperationException when invoked on a {@link Binding}
+   *      created via {@link com.google.inject.spi.Elements#getElements}. This
+   *      method is only supported on {@link Binding}s returned from an injector.
    */
   Provider<T> getProvider();
 
-  /**
-   * Gets the synthetic binding to this binding's Provider.
-   */
-  ProviderBinding<T> getProviderBinding();
+  <V> V acceptVisitor(Visitor<V> visitor);
 
-  /**
-   * Returns the scope applied by this binding.
-   */
-  Scope getScope();
+  <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor);
 
-  /**
-   * Accepts a binding visitor. Invokes the visitor method specific to this
-   * binding's type.
-   *
-   * @param visitor to call back on
-   */
-  void accept(BindingVisitor<? super T> visitor);
+  <V> V acceptScopingVisitor(ScopingVisitor<V> visitor);
+
+  interface TargetVisitor<T, V> {
+    V visitToInstance(T instance);
+    V visitToProvider(Provider<? extends T> provider);
+    V visitToProviderKey(Key<? extends Provider<? extends T>> providerKey);
+    V visitToKey(Key<? extends T> key);
+
+    // module-only bindings
+    V visitUntargetted();
+
+    // injector-only bindings
+    V visitConstructor(Constructor<? extends T> constructor);
+    V visitConstant(T value);
+    V visitConvertedConstant(T value);
+    V visitProviderBinding(Key<?> provided);
+  }
+
+  interface ScopingVisitor<V> {
+    V visitEagerSingleton();
+    V visitScope(Scope scope);
+    V visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation);
+    V visitNoScoping();
+  }
 }
diff --git a/src/com/google/inject/BindingImpl.java b/src/com/google/inject/BindingImpl.java
index 246e160..a1990c0 100644
--- a/src/com/google/inject/BindingImpl.java
+++ b/src/com/google/inject/BindingImpl.java
@@ -19,12 +19,14 @@
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
 import com.google.inject.internal.ToStringBuilder;
-import com.google.inject.spi.ProviderBinding;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.oldversion.OldVersionBinding;
+import com.google.inject.spi.oldversion.ProviderBinding;
 
 /**
  * @author crazybob@google.com (Bob Lee)
  */
-abstract class BindingImpl<T> implements Binding<T> {
+abstract class BindingImpl<T> implements OldVersionBinding<T> {
 
   final InjectorImpl injector;
   final Key<T> key;
@@ -87,6 +89,20 @@
     return loadStrategy;
   }
 
+  public <V> V acceptVisitor(Element.Visitor<V> visitor) {
+    return visitor.visitBinding(this);
+  }
+
+  public <V> V acceptScopingVisitor(ScopingVisitor<V> visitor) {
+    if (loadStrategy == LoadStrategy.EAGER) {
+      return visitor.visitEagerSingleton();
+    } else if (scope != Scopes.NO_SCOPE && scope != null) {
+      return visitor.visitScope(scope);
+    } else {
+      return visitor.visitNoScoping();
+    }
+  }
+
   /**
    * Perform any post-creation initialization, that could require construction
    * of other bindings.
diff --git a/src/com/google/inject/BoundProviderFactory.java b/src/com/google/inject/BoundProviderFactory.java
index 22c7fc1..4854062 100644
--- a/src/com/google/inject/BoundProviderFactory.java
+++ b/src/com/google/inject/BoundProviderFactory.java
@@ -16,7 +16,7 @@
 
 package com.google.inject;
 
-import com.google.inject.BindCommandProcessor.CreationListener;
+import com.google.inject.BindElementProcessor.CreationListener;
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
 import com.google.inject.spi.InjectionPoint;
diff --git a/src/com/google/inject/ClassBindingImpl.java b/src/com/google/inject/ClassBindingImpl.java
index b61ee4f..ee88668 100644
--- a/src/com/google/inject/ClassBindingImpl.java
+++ b/src/com/google/inject/ClassBindingImpl.java
@@ -20,9 +20,9 @@
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
 import com.google.inject.internal.ToStringBuilder;
-import com.google.inject.spi.BindingVisitor;
-import com.google.inject.spi.ClassBinding;
 import com.google.inject.spi.InjectionPoint;
+import com.google.inject.spi.oldversion.BindingVisitor;
+import com.google.inject.spi.oldversion.ClassBinding;
 import java.util.Collection;
 
 /**
@@ -50,6 +50,10 @@
     visitor.visit(this);
   }
 
+  public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor) {
+    return visitor.visitConstructor(lateBoundConstructor.getConstructor());
+  }
+
   @SuppressWarnings("unchecked")
   public Class<T> getBoundClass() {
     // T should always be the class itself.
diff --git a/src/com/google/inject/ConstantBindingImpl.java b/src/com/google/inject/ConstantBindingImpl.java
index b6d9c4f..c758c37 100644
--- a/src/com/google/inject/ConstantBindingImpl.java
+++ b/src/com/google/inject/ConstantBindingImpl.java
@@ -17,10 +17,10 @@
 
 package com.google.inject;
 
-import com.google.inject.spi.ConstantBinding;
-import com.google.inject.spi.BindingVisitor;
-import com.google.inject.util.Providers;
 import com.google.inject.internal.ToStringBuilder;
+import com.google.inject.spi.oldversion.BindingVisitor;
+import com.google.inject.spi.oldversion.ConstantBinding;
+import com.google.inject.util.Providers;
 
 /**
  * A constant binding.
@@ -42,6 +42,10 @@
     return this.provider;
   }
 
+  public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor) {
+    return visitor.visitConstant(value);
+  }
+
   public void accept(BindingVisitor<? super T> bindingVisitor) {
     bindingVisitor.visit(this);
   }
diff --git a/src/com/google/inject/ConstructionProxy.java b/src/com/google/inject/ConstructionProxy.java
index 39e3e3c..f11f656 100644
--- a/src/com/google/inject/ConstructionProxy.java
+++ b/src/com/google/inject/ConstructionProxy.java
@@ -39,5 +39,5 @@
    * Returns the injected constructor. If the injected constructor is synthetic (such as generated
    * code for method interception), the natural constructor is returned.
    */
-  Constructor getConstructor();
+  Constructor<T> getConstructor();
 }
diff --git a/src/com/google/inject/ConvertToTypesElementProcessor.java b/src/com/google/inject/ConvertToTypesElementProcessor.java
new file mode 100644
index 0000000..05e41f6
--- /dev/null
+++ b/src/com/google/inject/ConvertToTypesElementProcessor.java
@@ -0,0 +1,173 @@
+/**
+ * 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;
+
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.MatcherAndConverter;
+import com.google.inject.internal.SourceProvider;
+import com.google.inject.internal.Strings;
+import com.google.inject.matcher.AbstractMatcher;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.matcher.Matchers;
+import com.google.inject.spi.ConvertToTypes;
+import com.google.inject.spi.TypeConverter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * Handles {@link Binder#convertToTypes} commands.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+class ConvertToTypesElementProcessor extends ElementProcessor {
+
+  private final List<MatcherAndConverter> converters;
+
+  ConvertToTypesElementProcessor(Errors errors, List<MatcherAndConverter> converters) {
+    super(errors);
+    this.converters = converters;
+
+    // Configure type converters.
+    convertToPrimitiveType(int.class, Integer.class);
+    convertToPrimitiveType(long.class, Long.class);
+    convertToPrimitiveType(boolean.class, Boolean.class);
+    convertToPrimitiveType(byte.class, Byte.class);
+    convertToPrimitiveType(short.class, Short.class);
+    convertToPrimitiveType(float.class, Float.class);
+    convertToPrimitiveType(double.class, Double.class);
+
+    convertToClass(Character.class, new TypeConverter() {
+      public Object convert(String value, TypeLiteral<?> toType) {
+        value = value.trim();
+        if (value.length() != 1) {
+          throw new RuntimeException("Length != 1.");
+        }
+        return value.charAt(0);
+      }
+
+      @Override public String toString() {
+        return "TypeConverter<Character>";
+      }
+    });
+
+    convertToClasses(Matchers.subclassesOf(Enum.class), new TypeConverter() {
+      @SuppressWarnings("unchecked")
+      public Object convert(String value, TypeLiteral<?> toType) {
+        return Enum.valueOf((Class) toType.getRawType(), value);
+      }
+
+      @Override public String toString() {
+        return "TypeConverter<E extends Enum<E>>";
+      }
+    });
+
+    internalConvertToTypes(
+      new AbstractMatcher<TypeLiteral<?>>() {
+        public boolean matches(TypeLiteral<?> typeLiteral) {
+          return typeLiteral.getRawType() == Class.class;
+        }
+
+        @Override public String toString() {
+          return "Class<?>";
+        }
+      },
+      new TypeConverter() {
+        @SuppressWarnings("unchecked")
+        public Object convert(String value, TypeLiteral<?> toType) {
+          try {
+            return Class.forName(value);
+          }
+          catch (ClassNotFoundException e) {
+            throw new RuntimeException(e.getMessage());
+          }
+        }
+
+        @Override public String toString() {
+          return "TypeConverter<Class<?>>";
+        }
+      }
+    );
+  }
+
+  private <T> void convertToPrimitiveType(Class<T> primitiveType,
+      final Class<T> wrapperType) {
+    try {
+      final Method parser = wrapperType.getMethod(
+          "parse" + Strings.capitalize(primitiveType.getName()), String.class);
+
+      TypeConverter typeConverter = new TypeConverter() {
+        @SuppressWarnings("unchecked")
+        public Object convert(String value, TypeLiteral<?> toType) {
+          try {
+            return parser.invoke(null, value);
+          }
+          catch (IllegalAccessException e) {
+            throw new AssertionError(e);
+          }
+          catch (InvocationTargetException e) {
+            throw new RuntimeException(e.getTargetException().getMessage());
+          }
+        }
+
+        @Override public String toString() {
+          return "TypeConverter<" + wrapperType.getSimpleName() + ">";
+        }
+      };
+
+      convertToClass(wrapperType, typeConverter);
+    } catch (NoSuchMethodException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  private <T> void convertToClass(Class<T> type, TypeConverter converter) {
+    convertToClasses(Matchers.identicalTo(type), converter);
+  }
+
+  private void convertToClasses(final Matcher<? super Class<?>> typeMatcher,
+      TypeConverter converter) {
+    internalConvertToTypes(new AbstractMatcher<TypeLiteral<?>>() {
+      public boolean matches(TypeLiteral<?> typeLiteral) {
+        Type type = typeLiteral.getType();
+        if (!(type instanceof Class)) {
+          return false;
+        }
+        Class<?> clazz = (Class<?>) type;
+        return typeMatcher.matches(clazz);
+      }
+
+      @Override public String toString() {
+        return typeMatcher.toString();
+      }
+    }, converter);
+  }
+
+  private void internalConvertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
+      TypeConverter converter) {
+    converters.add(new MatcherAndConverter(typeMatcher, converter, SourceProvider.UNKNOWN_SOURCE));
+  }
+
+  @Override public Boolean visitConvertToTypes(ConvertToTypes command) {
+    converters.add(new MatcherAndConverter(
+        command.getTypeMatcher(), command.getTypeConverter(), command.getSource()));
+    return true;
+  }
+}
diff --git a/src/com/google/inject/DefaultConstructionProxyFactory.java b/src/com/google/inject/DefaultConstructionProxyFactory.java
index b508431..7be2de0 100644
--- a/src/com/google/inject/DefaultConstructionProxyFactory.java
+++ b/src/com/google/inject/DefaultConstructionProxyFactory.java
@@ -59,7 +59,7 @@
         public List<Parameter<?>> getParameters() {
           return parameters;
         }
-        public Constructor getConstructor() {
+        public Constructor<T> getConstructor() {
           return constructor;
         }
       };
@@ -77,7 +77,7 @@
       public List<Parameter<?>> getParameters() {
         return parameters;
       }
-      public Constructor getConstructor() {
+      public Constructor<T> getConstructor() {
         return constructor;
       }
     };
diff --git a/src/com/google/inject/ElementProcessor.java b/src/com/google/inject/ElementProcessor.java
new file mode 100644
index 0000000..4aa2962
--- /dev/null
+++ b/src/com/google/inject/ElementProcessor.java
@@ -0,0 +1,100 @@
+/**
+ * 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;
+
+import com.google.inject.internal.Errors;
+import com.google.inject.spi.BindConstant;
+import com.google.inject.spi.BindInterceptor;
+import com.google.inject.spi.BindScope;
+import com.google.inject.spi.ConvertToTypes;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.GetProvider;
+import com.google.inject.spi.Message;
+import com.google.inject.spi.RequestInjection;
+import com.google.inject.spi.RequestStaticInjection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Abstract base class for creating an injector from module elements.
+ *
+ * <p>Extending classes must return {@code true} from any overridden
+ * {@code visit*()} methods, in order for the element processor to remove the
+ * handled element.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+abstract class ElementProcessor implements Element.Visitor<Boolean> {
+
+  protected Errors errors;
+
+  protected ElementProcessor(Errors errors) {
+    this.errors = errors;
+  }
+
+  public void processCommands(List<Element> elements) {
+    Errors errorsAnyElement = this.errors;
+    try {
+      for (Iterator<Element> i = elements.iterator(); i.hasNext(); ) {
+        Element element = i.next();
+        this.errors = errorsAnyElement.withSource(element.getSource());
+        Boolean allDone = element.acceptVisitor(this);
+        if (allDone) {
+          i.remove();
+        }
+      }
+    } finally {
+      this.errors = errorsAnyElement;
+    }
+  }
+
+  public Boolean visitMessage(Message message) {
+    return false;
+  }
+
+  public Boolean visitBindInterceptor(BindInterceptor bindInterceptor) {
+    return false;
+  }
+
+  public Boolean visitBindScope(BindScope bindScope) {
+    return false;
+  }
+
+  public Boolean visitRequestInjection(RequestInjection requestInjection) {
+    return false;
+  }
+
+  public Boolean visitRequestStaticInjection(RequestStaticInjection requestStaticInjection) {
+    return false;
+  }
+
+  public Boolean visitBindConstant(BindConstant bindConstant) {
+    return false;
+  }
+
+  public Boolean visitConvertToTypes(ConvertToTypes convertToTypes) {
+    return false;
+  }
+
+  public <T> Boolean visitBinding(Binding<T> binding) {
+    return false;
+  }
+
+  public <T> Boolean visitGetProvider(GetProvider<T> getProvider) {
+    return false;
+  }
+}
diff --git a/src/com/google/inject/ErrorsElementProcessor.java b/src/com/google/inject/ErrorsElementProcessor.java
new file mode 100644
index 0000000..c1603d8
--- /dev/null
+++ b/src/com/google/inject/ErrorsElementProcessor.java
@@ -0,0 +1,54 @@
+/**
+ * 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;
+
+import com.google.inject.internal.Errors;
+import com.google.inject.spi.Message;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Handles {@link Binder#addError} commands.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+class ErrorsElementProcessor extends ElementProcessor {
+
+  private static final Logger logger = Logger.getLogger(Guice.class.getName());
+
+  ErrorsElementProcessor(Errors errors) {
+    super(errors);
+  }
+
+  @Override public Boolean visitMessage(Message message) {
+    if (message.getCause() != null) {
+      String rootMessage = getRootMessage(message.getCause());
+      logger.log(Level.INFO,
+          "An exception was caught and reported. Message: " + rootMessage,
+          message.getCause());
+    }
+
+    errors.addMessage(message);
+    return true;
+  }
+
+  public static String getRootMessage(Throwable t) {
+    Throwable cause = t.getCause();
+    return cause == null ? t.toString() : getRootMessage(cause);
+  }
+}
diff --git a/src/com/google/inject/FactoryProxy.java b/src/com/google/inject/FactoryProxy.java
index cf7fe4c..684ad2b 100644
--- a/src/com/google/inject/FactoryProxy.java
+++ b/src/com/google/inject/FactoryProxy.java
@@ -26,7 +26,7 @@
  * A placeholder which enables us to swap in the real factory once the
  * container is created.
  */
-class FactoryProxy<T> implements InternalFactory<T>, BindCommandProcessor.CreationListener {
+class FactoryProxy<T> implements InternalFactory<T>, BindElementProcessor.CreationListener {
 
   private final Key<T> key;
   private final Key<? extends T> targetKey;
@@ -53,7 +53,7 @@
     return targetFactory.get(errors, context, injectionPoint);
   }
 
-  public String toString() {
+  @Override public String toString() {
     return new ToStringBuilder(FactoryProxy.class)
         .add("key", key)
         .add("provider", targetFactory)
diff --git a/src/com/google/inject/GetProviderProcessor.java b/src/com/google/inject/GetProviderProcessor.java
index 7136116..b1bd696 100644
--- a/src/com/google/inject/GetProviderProcessor.java
+++ b/src/com/google/inject/GetProviderProcessor.java
@@ -16,9 +16,9 @@
 
 package com.google.inject;
 
-import com.google.inject.commands.GetProviderCommand;
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
+import com.google.inject.spi.GetProvider;
 
 /**
  * Handles {@link Binder#getProvider} commands.
@@ -26,7 +26,7 @@
  * @author crazybob@google.com (Bob Lee)
  * @author jessewilson@google.com (Jesse Wilson)
  */
-class GetProviderProcessor extends CommandProcessor {
+class GetProviderProcessor extends ElementProcessor {
 
   private final InjectorImpl injector;
 
@@ -35,7 +35,7 @@
     this.injector = injector;
   }
 
-  @Override public <T> Boolean visitGetProvider(GetProviderCommand<T> command) {
+  @Override public <T> Boolean visitGetProvider(GetProvider<T> command) {
     // ensure the provider can be created
     try {
       Provider<T> provider = injector.getProviderOrThrow(command.getKey(), errors);
diff --git a/src/com/google/inject/Guice.java b/src/com/google/inject/Guice.java
index 4d05713..7b81a1a 100644
--- a/src/com/google/inject/Guice.java
+++ b/src/com/google/inject/Guice.java
@@ -17,11 +17,10 @@
 package com.google.inject;
 
 import com.google.common.collect.Sets;
-import com.google.inject.commands.BindCommand;
-import com.google.inject.commands.BindConstantCommand;
-import com.google.inject.commands.Command;
-import com.google.inject.commands.CommandRecorder;
-import com.google.inject.commands.CommandReplayer;
+import com.google.inject.spi.BindConstant;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.Elements;
+import com.google.inject.spi.ModuleWriter;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
@@ -154,41 +153,40 @@
    * </pre>
    */
   public static Module overrideModule(Module module, Module overridesModule) {
-    CommandRecorder commandRecorder = new CommandRecorder();
-    final List<Command> commands = commandRecorder.recordCommands(module);
-    final List<Command> overrideCommands = commandRecorder.recordCommands(overridesModule);
+    final List<Element> elements = Elements.getElements(module);
+    final List<Element> overrideElements = Elements.getElements(overridesModule);
 
     return new AbstractModule() {
       public void configure() {
         final Set<Key> overriddenKeys = Sets.newHashSet();
 
         // execute the overrides module, keeping track of which keys were bound
-        new CommandReplayer() {
-          @Override public <T> void replayBind(Binder binder, BindCommand<T> command) {
-            overriddenKeys.add(command.getKey());
-            super.replayBind(binder, command);
+        new ModuleWriter() {
+          @Override public <T> void writeBind(Binder binder, Binding<T> binding) {
+            overriddenKeys.add(binding.getKey());
+            super.writeBind(binder, binding);
           }
-          @Override public void replayBindConstant(Binder binder, BindConstantCommand command) {
+          @Override public void writeBindConstant(Binder binder, BindConstant command) {
             overriddenKeys.add(command.getKey());
-            super.replayBindConstant(binder, command);
+            super.writeBindConstant(binder, command);
           }
-        }.replay(binder(), overrideCommands);
+        }.apply(binder(), overrideElements);
 
         // bind the regular module, skipping overridden keys. We only skip each
         // overridden key once, so things still blow up if the module binds the
         // same key multiple times
-        new CommandReplayer() {
-          @Override public <T> void replayBind(Binder binder, BindCommand<T> command) {
-            if (!overriddenKeys.remove(command.getKey())) {
-              super.replayBind(binder, command);
+        new ModuleWriter() {
+          @Override public <T> void writeBind(Binder binder, Binding<T> binding) {
+            if (!overriddenKeys.remove(binding.getKey())) {
+              super.writeBind(binder, binding);
             }
           }
-          @Override public void replayBindConstant(Binder binder, BindConstantCommand command) {
+          @Override public void writeBindConstant(Binder binder, BindConstant command) {
             if (!overriddenKeys.remove(command.getKey())) {
-              super.replayBindConstant(binder, command);
+              super.writeBindConstant(binder, command);
             }
           }
-        }.replay(binder(), commands);
+        }.apply(binder(), elements);
 
         // TODO: bind the overridden keys using multibinder
       }
diff --git a/src/com/google/inject/InjectorBuilder.java b/src/com/google/inject/InjectorBuilder.java
index 40df477..63b045f 100644
--- a/src/com/google/inject/InjectorBuilder.java
+++ b/src/com/google/inject/InjectorBuilder.java
@@ -21,12 +21,12 @@
 import com.google.common.collect.Lists;
 import com.google.inject.Reflection.Factory;
 import static com.google.inject.Scopes.SINGLETON;
-import com.google.inject.commands.Command;
-import com.google.inject.commands.CommandRecorder;
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
 import com.google.inject.internal.SourceProvider;
 import com.google.inject.internal.Stopwatch;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.Elements;
 import com.google.inject.spi.InjectionPoint;
 import java.lang.reflect.Member;
 import java.util.List;
@@ -50,10 +50,10 @@
   private InjectorImpl injector;
   private Errors errors = new Errors();
 
-  private final List<Command> commands = Lists.newArrayList();
+  private final List<Element> elements = Lists.newArrayList();
 
-  private BindCommandProcessor bindCommandProcesor;
-  private RequestInjectionCommandProcessor requestInjectionCommandProcessor;
+  private BindElementProcessor bindCommandProcesor;
+  private RequestInjectionElementProcessor requestInjectionCommandProcessor;
 
   /**
    * @param stage we're running in. If the stage is {@link Stage#PRODUCTION}, we will eagerly load
@@ -90,9 +90,7 @@
 
     modules.add(0, new BuiltInModule(injector, stage));
 
-    CommandRecorder commandRecorder = new CommandRecorder();
-    commandRecorder.setCurrentStage(stage);
-    commands.addAll(commandRecorder.recordCommands(modules));
+    elements.addAll(Elements.getElements(stage, modules));
 
     buildCoreInjector();
 
@@ -109,8 +107,8 @@
 
     fulfillInjectionRequests();
 
-    if (!commands.isEmpty()) {
-      throw new AssertionError("Failed to execute " + commands);
+    if (!elements.isEmpty()) {
+      throw new AssertionError("Failed to execute " + elements);
     }
 
     return injector;
@@ -118,27 +116,27 @@
 
   /** Builds the injector. */
   private void buildCoreInjector() {
-    new ErrorsCommandProcessor(errors)
-        .processCommands(commands);
+    new ErrorsElementProcessor(errors)
+        .processCommands(elements);
 
-    BindInterceptorCommandProcessor bindInterceptorCommandProcessor
-        = new BindInterceptorCommandProcessor(errors);
-    bindInterceptorCommandProcessor.processCommands(commands);
+    BindInterceptorElementProcessor bindInterceptorCommandProcessor
+        = new BindInterceptorElementProcessor(errors);
+    bindInterceptorCommandProcessor.processCommands(elements);
     ConstructionProxyFactory proxyFactory = bindInterceptorCommandProcessor.createProxyFactory();
     injector.reflection = reflectionFactory.create(proxyFactory);
     stopwatch.resetAndLog("Interceptors creation");
 
-    new ScopesCommandProcessor(errors, injector.scopes).processCommands(commands);
+    new ScopesElementProcessor(errors, injector.scopes).processCommands(elements);
     stopwatch.resetAndLog("Scopes creation");
 
-    new ConvertToTypesCommandProcessor(errors, injector.converters).processCommands(commands);
+    new ConvertToTypesElementProcessor(errors, injector.converters).processCommands(elements);
     stopwatch.resetAndLog("Converters creation");
 
     bindLogger();
-    bindCommandProcesor = new BindCommandProcessor(errors,
+    bindCommandProcesor = new BindElementProcessor(errors,
         injector, injector.scopes, injector.explicitBindings,
         injector.memberInjector);
-    bindCommandProcesor.processCommands(commands);
+    bindCommandProcesor.processCommands(elements);
     bindCommandProcesor.createUntargettedBindings();
     stopwatch.resetAndLog("Binding creation");
 
@@ -146,8 +144,8 @@
     stopwatch.resetAndLog("Binding indexing");
 
     requestInjectionCommandProcessor
-        = new RequestInjectionCommandProcessor(errors, injector.memberInjector);
-    requestInjectionCommandProcessor.processCommands(commands);
+        = new RequestInjectionElementProcessor(errors, injector.memberInjector);
+    requestInjectionCommandProcessor.processCommands(elements);
     stopwatch.resetAndLog("Static injection");
   }
 
@@ -162,7 +160,7 @@
     injector.memberInjector.validateOustandingInjections(errors);
     stopwatch.resetAndLog("Instance member validation");
 
-    new GetProviderProcessor(errors, injector).processCommands(commands);
+    new GetProviderProcessor(errors, injector).processCommands(elements);
     stopwatch.resetAndLog("Provider verification");
 
     errors.throwCreationExceptionIfErrorsExist();
diff --git a/src/com/google/inject/InjectorImpl.java b/src/com/google/inject/InjectorImpl.java
index b9f99d9..f7233da 100644
--- a/src/com/google/inject/InjectorImpl.java
+++ b/src/com/google/inject/InjectorImpl.java
@@ -30,16 +30,17 @@
 import com.google.inject.internal.MatcherAndConverter;
 import com.google.inject.internal.Nullability;
 import com.google.inject.internal.ReferenceCache;
-import com.google.inject.internal.SourceProvider;
 import com.google.inject.internal.StackTraceElements;
 import com.google.inject.internal.ToStringBuilder;
-import com.google.inject.spi.BindingVisitor;
-import com.google.inject.spi.ConvertedConstantBinding;
 import com.google.inject.spi.InjectionPoint;
-import com.google.inject.spi.ProviderBinding;
+import com.google.inject.spi.oldversion.BindingVisitor;
+import com.google.inject.spi.oldversion.ConvertedConstantBinding;
+import com.google.inject.spi.oldversion.OldVersionBinding;
+import com.google.inject.spi.oldversion.ProviderBinding;
 import com.google.inject.util.Providers;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Member;
@@ -150,12 +151,12 @@
   private <T> BindingImpl<T> getParentBinding(Key<T> key) {
     synchronized (parentBindings) {
       // null values will mean that the parent doesn't have this binding
-      Binding<T> binding = (Binding<T>) parentBindings.get(key);
+      OldVersionBinding<T> binding = (OldVersionBinding<T>) parentBindings.get(key);
       if (binding != null) {
         return (BindingImpl<T>) binding;
       }
       try {
-        binding = parentInjector.getBinding(key);
+        binding = (OldVersionBinding) parentInjector.getBinding(key);
       }
       catch (ProvisionException e) {
         // if this happens, the parent can't create this key, and we ignore it
@@ -239,7 +240,7 @@
   static class ProviderBindingImpl<T> extends BindingImpl<Provider<T>>
       implements ProviderBinding<T> {
 
-    final Binding<T> providedBinding;
+    final OldVersionBinding<T> providedBinding;
 
     ProviderBindingImpl(
         InjectorImpl injector,
@@ -249,11 +250,11 @@
       super(
           injector,
           key,
-          SourceProvider.UNKNOWN_SOURCE,
+          providedBinding.getSource(),
           createInternalFactory(providedBinding),
           Scopes.NO_SCOPE,
           loadStrategy);
-      this.providedBinding = providedBinding;
+      this.providedBinding = (OldVersionBinding<T>) providedBinding;
     }
 
     static <T> InternalFactory<Provider<T>> createInternalFactory(Binding<T> providedBinding) {
@@ -270,7 +271,11 @@
       bindingVisitor.visit(this);
     }
 
-    public Binding<T> getTarget() {
+    public <V> V acceptTargetVisitor(TargetVisitor<? super Provider<T>, V> visitor) {
+      return visitor.visitProviderBinding(providedBinding.getKey());
+    }
+
+    public OldVersionBinding<T> getTargetBinding() {
       return providedBinding;
     }
   }
@@ -344,7 +349,7 @@
 
     ConvertedConstantBindingImpl(
         InjectorImpl injector, Key<T> key, T value, Binding<String> originalBinding) {
-      super(injector, key, SourceProvider.UNKNOWN_SOURCE, new ConstantFactory<T>(value),
+      super(injector, key, originalBinding.getSource(), new ConstantFactory<T>(value),
           Scopes.NO_SCOPE, LoadStrategy.LAZY);
       this.value = value;
       provider = Providers.of(value);
@@ -363,8 +368,12 @@
       return value;
     }
 
-    public Binding<String> getOriginal() {
-      return originalBinding;
+    public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor) {
+      return visitor.visitConvertedConstant(value);
+    }
+
+    public OldVersionBinding<String> getOriginal() {
+      return (OldVersionBinding) originalBinding;
     }
 
     @Override public String toString() {
@@ -481,6 +490,11 @@
       constructorInjector = injector.getConstructor(implementation, errors);
     }
 
+    public Constructor<T> getConstructor() {
+      checkState(constructorInjector != null, "Constructor is not ready");
+      return constructorInjector.constructionProxy.getConstructor();
+    }
+
     @SuppressWarnings("unchecked")
     public T get(Errors errors, InternalContext context, InjectionPoint<?> injectionPoint)
         throws ErrorsException {
diff --git a/src/com/google/inject/InstanceBindingImpl.java b/src/com/google/inject/InstanceBindingImpl.java
index 547aeb0..9f61613 100644
--- a/src/com/google/inject/InstanceBindingImpl.java
+++ b/src/com/google/inject/InstanceBindingImpl.java
@@ -19,9 +19,9 @@
 
 import com.google.inject.internal.ErrorsException;
 import com.google.inject.internal.ToStringBuilder;
-import com.google.inject.spi.BindingVisitor;
 import com.google.inject.spi.InjectionPoint;
-import com.google.inject.spi.InstanceBinding;
+import com.google.inject.spi.oldversion.BindingVisitor;
+import com.google.inject.spi.oldversion.InstanceBinding;
 import com.google.inject.util.Providers;
 import java.util.Collection;
 
@@ -46,6 +46,10 @@
     bindingVisitor.visit(this);
   }
 
+  public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor) {
+    return visitor.visitToInstance(instance);
+  }
+
   public T getInstance() {
     return this.instance;
   }
diff --git a/src/com/google/inject/InvalidBindingImpl.java b/src/com/google/inject/InvalidBindingImpl.java
index 519ca02..62168c6 100644
--- a/src/com/google/inject/InvalidBindingImpl.java
+++ b/src/com/google/inject/InvalidBindingImpl.java
@@ -18,7 +18,7 @@
 
 import com.google.inject.internal.Errors;
 import com.google.inject.spi.InjectionPoint;
-import com.google.inject.spi.BindingVisitor;
+import com.google.inject.spi.oldversion.BindingVisitor;
 
 class InvalidBindingImpl<T> extends BindingImpl<T> {
 
@@ -34,6 +34,10 @@
     throw new AssertionError();
   }
 
+  public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> vVisitor) {
+    throw new UnsupportedOperationException();
+  }
+
   @Override public String toString() {
     return "InvalidBinding";
   }
diff --git a/src/com/google/inject/LinkedBindingImpl.java b/src/com/google/inject/LinkedBindingImpl.java
index 5b920f0..db273c6 100644
--- a/src/com/google/inject/LinkedBindingImpl.java
+++ b/src/com/google/inject/LinkedBindingImpl.java
@@ -17,8 +17,9 @@
 package com.google.inject;
 
 import com.google.inject.internal.ToStringBuilder;
-import com.google.inject.spi.BindingVisitor;
-import com.google.inject.spi.LinkedBinding;
+import com.google.inject.spi.oldversion.BindingVisitor;
+import com.google.inject.spi.oldversion.LinkedBinding;
+import com.google.inject.spi.oldversion.OldVersionBinding;
 
 /**
  *
@@ -41,7 +42,11 @@
     bindingVisitor.visit(this);
   }
 
-  public Binding<? extends T> getTarget() {
+  public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor) {
+    return visitor.visitToKey(targetKey);
+  }
+
+  public OldVersionBinding<? extends T> getTargetBinding() {
     return injector.getBinding(targetKey);
   }
 
diff --git a/src/com/google/inject/LinkedProviderBindingImpl.java b/src/com/google/inject/LinkedProviderBindingImpl.java
index 35263cc..e42eb45 100644
--- a/src/com/google/inject/LinkedProviderBindingImpl.java
+++ b/src/com/google/inject/LinkedProviderBindingImpl.java
@@ -17,8 +17,9 @@
 package com.google.inject;
 
 import com.google.inject.internal.ToStringBuilder;
-import com.google.inject.spi.BindingVisitor;
-import com.google.inject.spi.LinkedProviderBinding;
+import com.google.inject.spi.oldversion.BindingVisitor;
+import com.google.inject.spi.oldversion.LinkedProviderBinding;
+import com.google.inject.spi.oldversion.OldVersionBinding;
 
 /**
  *
@@ -41,10 +42,14 @@
     bindingVisitor.visit(this);
   }
 
-  public Binding<? extends Provider<? extends T>> getTargetProvider() {
+  public OldVersionBinding<? extends Provider<? extends T>> getTargetProvider() {
     return injector.getBinding(providerKey);
   }
 
+  public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor) {
+    return visitor.visitToProviderKey(providerKey);
+  }
+
   @Override public String toString() {
     return new ToStringBuilder(LinkedProviderBinding.class)
         .add("key", key)
diff --git a/src/com/google/inject/ProviderInstanceBindingImpl.java b/src/com/google/inject/ProviderInstanceBindingImpl.java
index 239d227..a96980b 100644
--- a/src/com/google/inject/ProviderInstanceBindingImpl.java
+++ b/src/com/google/inject/ProviderInstanceBindingImpl.java
@@ -18,9 +18,9 @@
 
 import com.google.inject.internal.ErrorsException;
 import com.google.inject.internal.ToStringBuilder;
-import com.google.inject.spi.BindingVisitor;
 import com.google.inject.spi.InjectionPoint;
-import com.google.inject.spi.ProviderInstanceBinding;
+import com.google.inject.spi.oldversion.BindingVisitor;
+import com.google.inject.spi.oldversion.ProviderInstanceBinding;
 import java.util.Collection;
 
 /**
@@ -44,6 +44,10 @@
     bindingVisitor.visit(this);
   }
 
+  public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor) {
+    return visitor.visitToProvider(providerInstance);
+  }
+
   public Provider<? extends T> getProviderInstance() {
     return this.providerInstance;
   }
diff --git a/src/com/google/inject/ProxyFactory.java b/src/com/google/inject/ProxyFactory.java
index d0da0ca..1754584 100644
--- a/src/com/google/inject/ProxyFactory.java
+++ b/src/com/google/inject/ProxyFactory.java
@@ -173,7 +173,7 @@
    * Creates a construction proxy given a class and parameter types.
    */
   private <T> ConstructionProxy<T> createConstructionProxy(Errors errors, final Class<?> clazz,
-      final Constructor standardConstructor) throws ErrorsException {
+      final Constructor<T> standardConstructor) throws ErrorsException {
     FastClass fastClass = newFastClass(clazz, Visibility.PUBLIC);
     final FastConstructor fastConstructor
         = fastClass.getConstructor(standardConstructor.getParameterTypes());
@@ -190,7 +190,7 @@
         return parameters;
       }
 
-      public Constructor getConstructor() {
+      public Constructor<T> getConstructor() {
         return standardConstructor;
       }
     };
diff --git a/src/com/google/inject/RequestInjectionElementProcessor.java b/src/com/google/inject/RequestInjectionElementProcessor.java
new file mode 100644
index 0000000..222b549
--- /dev/null
+++ b/src/com/google/inject/RequestInjectionElementProcessor.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;
+
+import com.google.common.collect.Lists;
+import com.google.inject.InjectorImpl.SingleMemberInjector;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.ErrorsException;
+import com.google.inject.spi.RequestInjection;
+import com.google.inject.spi.RequestStaticInjection;
+import java.util.List;
+
+/**
+ * Handles {@link Binder#requestInjection} and {@link Binder#requestStaticInjection} commands.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ * @author jessewilson@google.com (Jesse Wilson)
+ * @author mikeward@google.com (Mike Ward)
+ */
+class RequestInjectionElementProcessor extends ElementProcessor {
+
+  private final List<StaticInjection> staticInjections = Lists.newArrayList();
+  private final CreationTimeMemberInjector memberInjector;
+
+  RequestInjectionElementProcessor(Errors errors,
+      CreationTimeMemberInjector memberInjector) {
+    super(errors);
+    this.memberInjector = memberInjector;
+  }
+
+  @Override public Boolean visitRequestStaticInjection(RequestStaticInjection command) {
+    for (Class<?> type : command.getTypes()) {
+      staticInjections.add(new StaticInjection(command.getSource(), type));
+    }
+    return true;
+  }
+
+  @Override public Boolean visitRequestInjection(RequestInjection command) {
+    for (Object instance : command.getInstances()) {
+      memberInjector.requestInjection(instance, command.getSource());
+    }
+    return true;
+  }
+
+  public void validate(InjectorImpl injector) {
+    for (StaticInjection staticInjection : staticInjections) {
+      staticInjection.validate(injector);
+    }
+  }
+
+  public void injectMembers(InjectorImpl injector) {
+    for (StaticInjection staticInjection : staticInjections) {
+      staticInjection.injectMembers(injector);
+    }
+  }
+
+  /** A requested static injection. */
+  private class StaticInjection {
+    final Object source;
+    final Class<?> type;
+    final List<SingleMemberInjector> memberInjectors = Lists.newArrayList();
+
+    public StaticInjection(Object source, Class type) {
+      this.source = source;
+      this.type = type;
+    }
+
+    void validate(final InjectorImpl injector) {
+      Errors errorsForMember = errors.withSource(source);
+      injector.addSingleInjectorsForFields(
+          type.getDeclaredFields(), true, memberInjectors, errorsForMember);
+      injector.addSingleInjectorsForMethods(
+          type.getDeclaredMethods(), true, memberInjectors, errorsForMember);
+    }
+
+    void injectMembers(InjectorImpl injector) {
+      try {
+        injector.callInContext(new ContextualCallable<Void>() {
+          public Void call(InternalContext context) {
+            for (SingleMemberInjector injector : memberInjectors) {
+              injector.inject(errors, context, null);
+            }
+            return null;
+          }
+        });
+      } catch (ErrorsException e) {
+        throw new AssertionError();
+      }
+    }
+  }
+}
diff --git a/src/com/google/inject/ScopesElementProcessor.java b/src/com/google/inject/ScopesElementProcessor.java
new file mode 100644
index 0000000..8990f35
--- /dev/null
+++ b/src/com/google/inject/ScopesElementProcessor.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;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.inject.internal.Annotations;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.StackTraceElements;
+import com.google.inject.spi.BindScope;
+import java.lang.annotation.Annotation;
+import java.util.Map;
+
+/**
+ * Handles {@link Binder#bindScope} commands.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+class ScopesElementProcessor extends ElementProcessor {
+
+  private final Map<Class<? extends Annotation>, Scope> scopes;
+
+  ScopesElementProcessor(Errors errors,
+      Map<Class<? extends Annotation>, Scope> scopes) {
+    super(errors);
+    this.scopes = scopes;
+  }
+
+  @Override public Boolean visitBindScope(BindScope command) {
+    Scope scope = command.getScope();
+    Class<? extends Annotation> annotationType = command.getAnnotationType();
+
+    if (!Scopes.isScopeAnnotation(annotationType)) {
+      errors.withSource(StackTraceElements.forType(annotationType)).missingScopeAnnotation();
+      // Go ahead and bind anyway so we don't get collateral errors.
+    }
+
+    if (!Annotations.isRetainedAtRuntime(annotationType)) {
+      errors.withSource(StackTraceElements.forType(annotationType))
+          .missingRuntimeRetention(command.getSource());
+      // Go ahead and bind anyway so we don't get collateral errors.
+    }
+
+    Scope existing = scopes.get(checkNotNull(annotationType, "annotation type"));
+    if (existing != null) {
+      errors.duplicateScopes(existing, annotationType, scope);
+    } else {
+      scopes.put(annotationType, checkNotNull(scope, "scope"));
+    }
+
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/src/com/google/inject/commands/AddMessageCommand.java b/src/com/google/inject/commands/AddMessageCommand.java
deleted file mode 100644
index 7ed6145..0000000
--- a/src/com/google/inject/commands/AddMessageCommand.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.inject.commands;
-
-import com.google.common.collect.ImmutableList;
-import com.google.inject.spi.InjectionPoint;
-import com.google.inject.spi.Message;
-
-/**
- * Immutable snapshot of a request to add a string message.
- *
- * @author jessewilson@google.com (Jesse Wilson)
- */
-public final class AddMessageCommand implements Command {
-  private final Message message;
-
-  AddMessageCommand(Message message) {
-    this.message = message;
-  }
-
-  AddMessageCommand(Object source, String message, Object[] arguments) {
-    this.message = new Message(source, String.format(message, arguments));
-  }
-
-  AddMessageCommand(Object source, Throwable throwable) {
-    this.message = new Message(source,
-        "An exception was caught and reported. Message: " + throwable.getMessage(), 
-        ImmutableList.<InjectionPoint>of(), throwable);
-  }
-
-  public Object getSource() {
-    return message.getSource();
-  }
-
-  public <T> T acceptVisitor(Visitor<T> visitor) {
-    return visitor.visitAddMessage(this);
-  }
-
-  public Message getMessage() {
-    return message;
-  }
-}
diff --git a/src/com/google/inject/internal/ModuleBinding.java b/src/com/google/inject/internal/ModuleBinding.java
new file mode 100644
index 0000000..94f90ce
--- /dev/null
+++ b/src/com/google/inject/internal/ModuleBinding.java
@@ -0,0 +1,303 @@
+/**
+ * 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.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.inject.Binder;
+import com.google.inject.Binding;
+import com.google.inject.Key;
+import com.google.inject.Provider;
+import com.google.inject.Scope;
+import com.google.inject.TypeLiteral;
+import com.google.inject.binder.AnnotatedBindingBuilder;
+import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.binder.ScopedBindingBuilder;
+import com.google.inject.spi.DefaultBindTargetVisitor;
+import java.lang.annotation.Annotation;
+
+/**
+ * Immutable snapshot of a request to bind a value.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class ModuleBinding<T> implements Binding<T> {
+
+  private static final Target<Object> EMPTY_TARGET = new Target<Object>() {
+    public <V> V acceptTargetVisitor(TargetVisitor<? super Object, V> visitor) {
+      return visitor.visitUntargetted();
+    }
+  };
+
+  private static final Scoping EMPTY_SCOPING = new AbstractScoping() {
+    public <V> V acceptVisitor(ScopingVisitor<V> visitor) {
+      return visitor.visitNoScoping();
+    }
+  };
+
+  private static final TargetVisitor<Object, Boolean> SUPPORTS_SCOPES
+      = new DefaultBindTargetVisitor<Object, Boolean>() {
+    @Override protected Boolean visitTarget() {
+      return true;
+    }
+
+    @Override public Boolean visitToInstance(Object instance) {
+      return false;
+    }
+  };
+
+  private final Object source;
+  private Key<T> key;
+
+  @SuppressWarnings("unchecked")
+  private Target<T> target = (Target<T>) EMPTY_TARGET;
+  private Scoping scoping = EMPTY_SCOPING;
+
+  public ModuleBinding(Object source, Key<T> key) {
+    this.source = checkNotNull(source, "source");
+    this.key = checkNotNull(key, "key");
+  }
+
+  public Object getSource() {
+    return source;
+  }
+
+  /**
+   * Returns the scoped provider guice uses to fulfill requests for this
+   * binding.
+   */
+  public Provider<T> getProvider() {
+    throw new UnsupportedOperationException();
+  }
+
+  public <V> V acceptVisitor(Visitor<V> visitor) {
+    return visitor.visitBinding(this);
+  }
+
+  public Key<T> getKey() {
+    return key;
+  }
+
+  public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor) {
+    return target.acceptTargetVisitor(visitor);
+  }
+
+  public <V> V acceptScopingVisitor(ScopingVisitor<V> visitor) {
+    return scoping.acceptVisitor(visitor);
+  }
+
+  @Override public String toString() {
+    return "bind " + key
+        + (target == EMPTY_TARGET ? "" : (" to " + target))
+        + (scoping == EMPTY_SCOPING ? "" : (" in " + scoping));
+  }
+
+  private static abstract class AbstractScoping implements Scoping {
+    public Scope getScope() {
+      return null;
+    }
+    public Class<? extends Annotation> getScopeAnnotation() {
+      return null;
+    }
+  }
+
+  public BindingBuilder bindingBuilder(Binder binder) {
+    return new BindingBuilder(binder);
+  }
+
+  /**
+   * Write access to the internal state of this command. Not for use by the public API.
+   */
+  public class BindingBuilder implements AnnotatedBindingBuilder<T> {
+    private final Binder binder;
+
+    BindingBuilder(Binder binder) {
+      this.binder = binder.skipSources(BindingBuilder.class);
+    }
+
+    public LinkedBindingBuilder<T> annotatedWith(
+        Class<? extends Annotation> annotationType) {
+      checkNotNull(annotationType, "annotationType");
+      checkNotAnnotated();
+      key = Key.get(key.getTypeLiteral(), annotationType);
+      return this;
+    }
+
+    public LinkedBindingBuilder<T> annotatedWith(Annotation annotation) {
+      checkNotNull(annotation, "annotation");
+      checkNotAnnotated();
+      key = Key.get(key.getTypeLiteral(), annotation);
+      return this;
+    }
+
+    public ScopedBindingBuilder to(final Class<? extends T> implementation) {
+      return to(Key.get(implementation));
+    }
+
+    public ScopedBindingBuilder to(
+        final TypeLiteral<? extends T> implementation) {
+      return to(Key.get(implementation));
+    }
+
+    public ScopedBindingBuilder to(final Key<? extends T> targetKey) {
+      checkNotNull(targetKey, "targetKey");
+      checkNotTargetted();
+      target = new Target<T>() {
+        public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor) {
+          return visitor.visitToKey(targetKey);
+        }
+        @Override public String toString() {
+          return String.valueOf(targetKey);
+        }
+      };
+      return this;
+    }
+
+    public void toInstance(final T instance) {
+      checkNotTargetted();
+      target = new Target<T>() {
+        public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor) {
+          return visitor.visitToInstance(instance);
+        }
+      };
+    }
+
+    public ScopedBindingBuilder toProvider(final Provider<? extends T> provider) {
+      checkNotNull(provider, "provider");
+      checkNotTargetted();
+      target = new Target<T>() {
+        public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor) {
+          return visitor.visitToProvider(provider);
+        }
+      };
+      return this;
+    }
+
+    public ScopedBindingBuilder toProvider(
+        Class<? extends Provider<? extends T>> providerType) {
+      return toProvider(Key.get(providerType));
+    }
+
+    public ScopedBindingBuilder toProvider(
+        final Key<? extends Provider<? extends T>> providerKey) {
+      checkNotNull(providerKey, "providerKey");
+      checkNotTargetted();
+      target = new Target<T>() {
+        public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor) {
+          return visitor.visitToProviderKey(providerKey);
+        }
+      };
+      return this;
+    }
+
+    public void in(final Class<? extends Annotation> scopeAnnotation) {
+      checkNotNull(scopeAnnotation, "scopeAnnotation");
+      checkNotScoped();
+
+      scoping = new AbstractScoping() {
+        @Override public Class<? extends Annotation> getScopeAnnotation() {
+          return scopeAnnotation;
+        }
+        public <V> V acceptVisitor(ScopingVisitor<V> visitor) {
+          return visitor.visitScopeAnnotation(scopeAnnotation);
+        }
+        @Override public String toString() {
+          return scopeAnnotation.getName();
+        }
+      };
+    }
+
+    public void in(final Scope scope) {
+      checkNotNull(scope, "scope");
+      checkNotScoped();
+      scoping = new AbstractScoping() {
+        @Override public Scope getScope() {
+          return scope;
+        }
+        public <V> V acceptVisitor(ScopingVisitor<V> visitor) {
+          return visitor.visitScope(scope);
+        }
+        @Override public String toString() {
+          return String.valueOf(scope);
+        }
+      };
+    }
+
+    public void asEagerSingleton() {
+      checkNotScoped();
+      scoping = new AbstractScoping() {
+        public <V> V acceptVisitor(ScopingVisitor<V> visitor) {
+          return visitor.visitEagerSingleton();
+        }
+        @Override public String toString() {
+          return "eager singleton";
+        }
+      };
+    }
+
+    static final String IMPLEMENTATION_ALREADY_SET
+        = "Implementation is set more than once.";
+    static final String SINGLE_INSTANCE_AND_SCOPE = "Setting the scope is not"
+        + " permitted when binding to a single instance.";
+    static final String SCOPE_ALREADY_SET = "Scope is set more than once.";
+    static final String ANNOTATION_ALREADY_SPECIFIED = "More than one annotation"
+        + " is specified for this binding.";
+
+    private void checkNotTargetted() {
+      if (target != EMPTY_TARGET) {
+        binder.addError(IMPLEMENTATION_ALREADY_SET);
+      }
+    }
+
+    private void checkNotAnnotated() {
+      if (ModuleBinding.this.key.getAnnotationType() != null) {
+        binder.addError(ANNOTATION_ALREADY_SPECIFIED);
+      }
+    }
+
+    private void checkNotScoped() {
+      @SuppressWarnings("unchecked") TargetVisitor<T,Boolean> supportsScopesOfT
+          = (TargetVisitor<T,Boolean>) SUPPORTS_SCOPES;
+
+      // Scoping isn't allowed when we have only one instance.
+      if (!target.acceptTargetVisitor(supportsScopesOfT)) {
+        binder.addError(SINGLE_INSTANCE_AND_SCOPE);
+        return;
+      }
+
+      if (scoping != EMPTY_SCOPING) {
+        binder.addError(SCOPE_ALREADY_SET);
+      }
+    }
+
+    @Override public String toString() {
+      String type = key.getAnnotationType() == null
+          ? "AnnotatedBindingBuilder<"
+          : "LinkedBindingBuilder<";
+      return type + key.getTypeLiteral() + ">";
+    }
+  }
+
+  /** A binding target, which provides instances from a specific key. */
+  private interface Target<T> {
+    <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor);
+  }
+
+  /** Immutable snapshot of a binding scope. */
+  private interface Scoping {
+    <V> V acceptVisitor(ScopingVisitor<V> visitor);
+  }
+}
diff --git a/src/com/google/inject/spi/BindConstant.java b/src/com/google/inject/spi/BindConstant.java
new file mode 100644
index 0000000..3189dd8
--- /dev/null
+++ b/src/com/google/inject/spi/BindConstant.java
@@ -0,0 +1,226 @@
+/**
+ * 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.spi;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.inject.Binder;
+import com.google.inject.Binding.TargetVisitor;
+import com.google.inject.Key;
+import com.google.inject.binder.AnnotatedConstantBindingBuilder;
+import com.google.inject.binder.ConstantBindingBuilder;
+import java.lang.annotation.Annotation;
+
+/**
+ * Immutable snapshot of a request to bind a constant.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class BindConstant implements Element {
+
+  // TODO(jessewilson): Combine with Binding
+
+  private static final ConstantTarget<Object> UNTARGETTED
+      = new ConstantTarget<Object>(null, Void.class) {
+    public <V> V acceptTargetVisitor(TargetVisitor<? super Object, V> visitor) {
+      return visitor.visitUntargetted();
+    }
+  };
+
+  private final Object source;
+  private BindingAnnotation bindingAnnotation;
+  private ConstantTarget<?> target = UNTARGETTED;
+
+  BindConstant(Object source) {
+    this.source = checkNotNull(source, "source");
+  }
+
+  public Object getSource() {
+    return source;
+  }
+
+  public <T> T acceptVisitor(Visitor<T> visitor) {
+    return visitor.visitBindConstant(this);
+  }
+
+  public <T> Key<T> getKey() {
+    return bindingAnnotation.getKey();
+  }
+
+  public <V> V acceptTargetVisitor(TargetVisitor<?, V> visitor) {
+    TargetVisitor v = (TargetVisitor) visitor;
+    return (V) target.acceptTargetVisitor(v);
+  }
+
+  /**
+   * Target API for bindConstant().
+   */
+  private static class ConstantTarget<T> {
+    private final T value;
+    private final Class<?> type;
+
+    protected ConstantTarget(T value, Class<?> type) {
+      this.value = value;
+      this.type = type;
+    }
+
+    /**
+     * Returns the type of constant, such as {@code Integer.class} or
+     * {@code Enum.class}.
+     */
+    public Class getType() {
+      return type;
+    }
+    public <V> V acceptTargetVisitor(TargetVisitor<? super T, V> visitor) {
+      return visitor.visitToInstance(value);
+    }
+    @Override public String toString() {
+      return String.valueOf(value);
+    }
+  }
+
+  /**
+   * Internal annotation API.
+   */
+  private abstract class BindingAnnotation {
+    abstract ConstantBindingBuilder execute(AnnotatedConstantBindingBuilder builder);
+    abstract <T> Key<T> getKey();
+  }
+
+  BindingBuilder bindingBuilder(Binder binder) {
+    return new BindingBuilder(binder);
+  }
+
+  /**
+   * Package-private write access to the internal state of this command.
+   */
+  class BindingBuilder
+      implements AnnotatedConstantBindingBuilder, ConstantBindingBuilder {
+    private final Binder binder;
+
+    BindingBuilder(Binder binder) {
+      this.binder = binder.skipSources(BindingBuilder.class);
+    }
+
+    public ConstantBindingBuilder annotatedWith(final Class<? extends Annotation> annotationType) {
+      checkNotNull(annotationType, "annotationType");
+      assertNoBindingAnnotation();
+
+      bindingAnnotation = new BindingAnnotation() {
+        public ConstantBindingBuilder execute(AnnotatedConstantBindingBuilder builder) {
+          return builder.annotatedWith(annotationType);
+        }
+        @SuppressWarnings({"unchecked"})
+        public <T> Key<T> getKey() {
+          return Key.get((Class<T>) target.getType(), annotationType);
+        }
+      };
+      return this;
+    }
+
+    public ConstantBindingBuilder annotatedWith(final Annotation annotation) {
+      checkNotNull(annotation, "annotation");
+      assertNoBindingAnnotation();
+
+      bindingAnnotation = new BindingAnnotation() {
+        public ConstantBindingBuilder execute(AnnotatedConstantBindingBuilder builder) {
+          return builder.annotatedWith(annotation);
+        }
+        @SuppressWarnings({"unchecked"})
+        public <T> Key<T> getKey() {
+          return Key.get((Class<T>) target.getType(), annotation);
+        }
+      };
+      return this;
+    }
+
+    public void to(final String value) {
+      checkNotNull(value, "value");
+      assertNoTarget();
+      BindConstant.this.target = new ConstantTarget<String>(value, String.class);
+    }
+
+    public void to(final int value) {
+      assertNoTarget();
+      BindConstant.this.target = new ConstantTarget<Integer>(value, Integer.class);
+    }
+
+    public void to(final long value) {
+      assertNoTarget();
+      BindConstant.this.target = new ConstantTarget<Long>(value, Long.class);
+    }
+
+    public void to(final boolean value) {
+      assertNoTarget();
+      BindConstant.this.target = new ConstantTarget<Boolean>(value, Boolean.class);
+    }
+
+    public void to(final double value) {
+      assertNoTarget();
+      BindConstant.this.target = new ConstantTarget<Double>(value, Double.class);
+    }
+
+    public void to(final float value) {
+      assertNoTarget();
+      BindConstant.this.target = new ConstantTarget<Float>(value, Float.class);
+    }
+
+    public void to(final short value) {
+      assertNoTarget();
+      BindConstant.this.target = new ConstantTarget<Short>(value, Short.class);
+    }
+
+    public void to(final char value) {
+      assertNoTarget();
+      BindConstant.this.target = new ConstantTarget<Character>(value, Character.class);
+    }
+
+    public void to(final Class<?> value) {
+      checkNotNull(value, "value");
+      assertNoTarget();
+      BindConstant.this.target = new ConstantTarget<Class<?>>(value, Class.class);
+    }
+
+    public <E extends Enum<E>> void to(final E value) {
+      checkNotNull(value, "value");
+      assertNoTarget();
+      BindConstant.this.target = new ConstantTarget<Enum>(value, value.getDeclaringClass());
+    }
+
+    static final String CONSTANT_VALUE_ALREADY_SET = "Constant value is set more"
+        + " than once.";
+    static final String ANNOTATION_ALREADY_SPECIFIED = "More than one annotation"
+        + " is specified for this binding.";
+
+    private void assertNoBindingAnnotation() {
+      if (bindingAnnotation != null) {
+        binder.addError(ANNOTATION_ALREADY_SPECIFIED);
+      }
+    }
+
+    private void assertNoTarget() {
+      if (target != UNTARGETTED) {
+        binder.addError(CONSTANT_VALUE_ALREADY_SET);
+      }
+    }
+
+    @Override public String toString() {
+      return bindingAnnotation == null
+          ? "AnnotatedConstantBindingBuilder"
+          : "ConstantBindingBuilder";
+    }
+  }
+}
diff --git a/src/com/google/inject/spi/BindInterceptor.java b/src/com/google/inject/spi/BindInterceptor.java
new file mode 100644
index 0000000..8fa916c
--- /dev/null
+++ b/src/com/google/inject/spi/BindInterceptor.java
@@ -0,0 +1,68 @@
+/**
+ * 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.spi;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.inject.matcher.Matcher;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import static java.util.Collections.unmodifiableList;
+import java.util.List;
+import org.aopalliance.intercept.MethodInterceptor;
+
+/**
+ * Immutable snapshot of a request to bind an interceptor.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class BindInterceptor implements Element {
+  private final Object source;
+  private final Matcher<? super Class<?>> classMatcher;
+  private final Matcher<? super Method> methodMatcher;
+  private final List<MethodInterceptor> interceptors;
+
+  BindInterceptor(
+      Object source,
+      Matcher<? super Class<?>> classMatcher,
+      Matcher<? super Method> methodMatcher,
+      MethodInterceptor[] interceptors) {
+    this.source = checkNotNull(source, "source");
+    this.classMatcher = checkNotNull(classMatcher, "classMatcher");
+    this.methodMatcher = checkNotNull(methodMatcher, "methodMatcher");
+    this.interceptors = unmodifiableList(Arrays.asList(interceptors.clone()));
+  }
+
+  public Object getSource() {
+    return source;
+  }
+
+  public Matcher<? super Class<?>> getClassMatcher() {
+    return classMatcher;
+  }
+
+  public Matcher<? super Method> getMethodMatcher() {
+    return methodMatcher;
+  }
+
+  public List<MethodInterceptor> getInterceptors() {
+    return interceptors;
+  }
+
+  public <T> T acceptVisitor(Visitor<T> visitor) {
+    return visitor.visitBindInterceptor(this);
+  }
+}
diff --git a/src/com/google/inject/spi/BindScope.java b/src/com/google/inject/spi/BindScope.java
new file mode 100644
index 0000000..4731bcb
--- /dev/null
+++ b/src/com/google/inject/spi/BindScope.java
@@ -0,0 +1,54 @@
+/**
+ * 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.spi;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.inject.Scope;
+import java.lang.annotation.Annotation;
+
+/**
+ * Immutable snapshot of a request to bind a scope.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class BindScope implements Element {
+  private final Object source;
+  private final Class<? extends Annotation> annotationType;
+  private final Scope scope;
+
+  BindScope(Object source, Class<? extends Annotation> annotationType, Scope scope) {
+    this.source = checkNotNull(source, "source");
+    this.annotationType = checkNotNull(annotationType, "annotationType");
+    this.scope = checkNotNull(scope, "scope");
+  }
+
+  public Object getSource() {
+    return source;
+  }
+
+  public Class<? extends Annotation> getAnnotationType() {
+    return annotationType;
+  }
+
+  public Scope getScope() {
+    return scope;
+  }
+
+  public <T> T acceptVisitor(Visitor<T> visitor) {
+    return visitor.visitBindScope(this);
+  }
+}
diff --git a/src/com/google/inject/spi/ConvertToTypes.java b/src/com/google/inject/spi/ConvertToTypes.java
new file mode 100644
index 0000000..2443166
--- /dev/null
+++ b/src/com/google/inject/spi/ConvertToTypes.java
@@ -0,0 +1,55 @@
+/**
+ * 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.spi;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.inject.TypeLiteral;
+import com.google.inject.matcher.Matcher;
+
+/**
+ * Immutable snapshot of a request to convert binder types.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class ConvertToTypes implements Element {
+  private final Object source;
+  private final Matcher<? super TypeLiteral<?>> typeMatcher;
+  private final TypeConverter typeConverter;
+
+  ConvertToTypes(Object source, Matcher<? super TypeLiteral<?>> typeMatcher,
+      TypeConverter typeConverter) {
+    this.source = checkNotNull(source, "source");
+    this.typeMatcher = checkNotNull(typeMatcher, "typeMatcher");
+    this.typeConverter = checkNotNull(typeConverter, "typeConverter");
+  }
+
+  public Object getSource() {
+    return source;
+  }
+
+  public Matcher<? super TypeLiteral<?>> getTypeMatcher() {
+    return typeMatcher;
+  }
+
+  public TypeConverter getTypeConverter() {
+    return typeConverter;
+  }
+
+  public <T> T acceptVisitor(Visitor<T> visitor) {
+    return visitor.visitConvertToTypes(this);
+  }
+}
diff --git a/src/com/google/inject/spi/DefaultBindTargetVisitor.java b/src/com/google/inject/spi/DefaultBindTargetVisitor.java
new file mode 100644
index 0000000..98046de
--- /dev/null
+++ b/src/com/google/inject/spi/DefaultBindTargetVisitor.java
@@ -0,0 +1,65 @@
+/**
+ * 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.spi;
+
+import com.google.inject.Binding.TargetVisitor;
+import com.google.inject.Key;
+import com.google.inject.Provider;
+import java.lang.reflect.Constructor;
+
+public class DefaultBindTargetVisitor<T, V> implements TargetVisitor<T, V> {
+
+  protected V visitTarget() {
+    return null;
+  }
+
+  public V visitToInstance(T instance) {
+    return visitTarget();
+  }
+
+  public V visitToProvider(Provider<? extends T> provider) {
+    return visitTarget();
+  }
+
+  public V visitToProviderKey(Key<? extends Provider<? extends T>> providerKey) {
+    return visitTarget();
+  }
+
+  public V visitToKey(Key<? extends T> key) {
+    return visitTarget();
+  }
+
+  public V visitUntargetted() {
+    return visitTarget();
+  }
+
+  public V visitConstructor(Constructor<? extends T> constructor) {
+    return visitTarget();
+  }
+
+  public V visitConstant(T value) {
+    return visitTarget();
+  }
+
+  public V visitConvertedConstant(T value) {
+    return visitTarget();
+  }
+
+  public V visitProviderBinding(Key<?> provided) {
+    return visitTarget();
+  }
+}
diff --git a/src/com/google/inject/spi/DefaultElementVisitor.java b/src/com/google/inject/spi/DefaultElementVisitor.java
new file mode 100644
index 0000000..e2d5358
--- /dev/null
+++ b/src/com/google/inject/spi/DefaultElementVisitor.java
@@ -0,0 +1,74 @@
+/**
+ * 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.spi;
+
+import com.google.inject.Binding;
+
+/**
+ * No-op visitor for subclassing. All interface methods simply delegate to
+ * {@link #visitElement(Element)}, returning its result.
+ *
+ * @author sberlin@gmail.com (Sam Berlin)
+ */
+public class DefaultElementVisitor<V> implements Element.Visitor<V> {
+
+  protected DefaultElementVisitor() {}
+
+  /**
+   * Visit {@code element} and return a result.
+   */
+  protected V visitElement(Element element) {
+    return null;
+  }
+
+  public V visitMessage(Message message) {
+    return visitElement(message);
+  }
+
+  public <T> V visitBinding(Binding<T> command) {
+    return visitElement(command);
+  }
+
+  public V visitBindConstant(BindConstant command) {
+    return visitElement(command);
+  }
+
+  public V visitBindInterceptor(BindInterceptor command) {
+    return visitElement(command);
+  }
+
+  public V visitBindScope(BindScope command) {
+    return visitElement(command);
+  }
+
+  public V visitConvertToTypes(ConvertToTypes command) {
+    return visitElement(command);
+  }
+
+  public <T> V visitGetProvider(GetProvider<T> command) {
+    return visitElement(command);
+  }
+
+  public V visitRequestInjection(RequestInjection command) {
+    return visitElement(command);
+  }
+
+  public V visitRequestStaticInjection(RequestStaticInjection command) {
+    return visitElement(command);
+  }
+}
diff --git a/src/com/google/inject/spi/Element.java b/src/com/google/inject/spi/Element.java
new file mode 100644
index 0000000..43d16f2
--- /dev/null
+++ b/src/com/google/inject/spi/Element.java
@@ -0,0 +1,44 @@
+/**
+ * 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.spi;
+
+import com.google.inject.Binding;
+
+/**
+ * Immutable snapshot of a binding command.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public interface Element {
+  Object getSource();
+  <T> T acceptVisitor(Visitor<T> visitor);
+
+  /**
+   * Visit commands.
+   */
+  public interface Visitor<V> {
+    V visitMessage(Message message);
+    V visitBindInterceptor(BindInterceptor bindInterceptor);
+    V visitBindScope(BindScope bindScope);
+    V visitRequestInjection(RequestInjection requestInjection);
+    V visitRequestStaticInjection(RequestStaticInjection requestStaticInjection);
+    V visitBindConstant(BindConstant bindConstant);
+    V visitConvertToTypes(ConvertToTypes convertToTypes);
+    <T> V visitBinding(Binding<T> binding);
+    <T> V visitGetProvider(GetProvider<T> getProvider);
+  }
+}
diff --git a/src/com/google/inject/spi/Elements.java b/src/com/google/inject/spi/Elements.java
new file mode 100644
index 0000000..d1f6857
--- /dev/null
+++ b/src/com/google/inject/spi/Elements.java
@@ -0,0 +1,245 @@
+/**
+ * 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.spi;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+import com.google.inject.Binding.TargetVisitor;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.Scope;
+import com.google.inject.Stage;
+import com.google.inject.TypeLiteral;
+import com.google.inject.binder.AnnotatedBindingBuilder;
+import com.google.inject.binder.AnnotatedConstantBindingBuilder;
+import com.google.inject.internal.ModuleBinding;
+import com.google.inject.internal.SourceProvider;
+import com.google.inject.matcher.Matcher;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.aopalliance.intercept.MethodInterceptor;
+
+/**
+ * Records elements executed by a module so they can be inspected or
+ * {@link ModuleWriter replayed}.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class Elements {
+  private static final TargetVisitor<Object, Object> GET_INSTANCE_VISITOR
+      = new DefaultBindTargetVisitor<Object, Object>() {
+    @Override public Object visitToInstance(Object instance) {
+      return instance;
+    }
+
+    @Override protected Object visitTarget() {
+      throw new IllegalArgumentException();
+    }
+  };
+
+  /**
+   * Records the elements executed by {@code modules}.
+   */
+  public static List<Element> getElements(Module... modules) {
+    return getElements(Stage.DEVELOPMENT, Arrays.asList(modules));
+  }
+
+  /**
+   * Records the elements executed by {@code modules}.
+   */
+  public static List<Element> getElements(Stage stage, Module... modules) {
+    return getElements(stage, Arrays.asList(modules));
+  }
+
+  /**
+   * Records the elements executed by {@code modules}.
+   */
+  public static List<Element> getElements(Iterable<? extends Module> modules) {
+    return getElements(Stage.DEVELOPMENT, modules);
+  }
+
+  /**
+   * Records the elements executed by {@code modules}.
+   */
+  public static List<Element> getElements(Stage stage, Iterable<? extends Module> modules) {
+    RecordingBinder binder = new RecordingBinder(stage);
+    for (Module module : modules) {
+      binder.install(module);
+    }
+    return Collections.unmodifiableList(binder.elements);
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <T> com.google.inject.Binding.TargetVisitor<T, T> getInstanceVisitor() {
+    return (com.google.inject.Binding.TargetVisitor<T, T>) GET_INSTANCE_VISITOR;
+  }
+
+  private static class RecordingBinder implements Binder {
+    private final Stage stage;
+    private final Set<Module> modules;
+    private final List<Element> elements;
+    private final Object source;
+    private final SourceProvider sourceProvider;
+
+    private RecordingBinder(Stage stage) {
+      this.stage = stage;
+      this.modules = Sets.newHashSet();
+      this.elements = Lists.newArrayList();
+      this.source = null;
+      this.sourceProvider = new SourceProvider()
+          .plusSkippedClasses(Elements.class, RecordingBinder.class, AbstractModule.class);
+    }
+
+    /**
+     * Creates a recording binder that's backed by the same configuration as
+     * {@code backingBinder}.
+     */
+    private RecordingBinder(RecordingBinder parent, Object source, SourceProvider sourceProvider) {
+      checkArgument(source == null ^ sourceProvider == null);
+
+      this.stage = parent.stage;
+      this.modules = parent.modules;
+      this.elements = parent.elements;
+      this.source = source;
+      this.sourceProvider = sourceProvider;
+    }
+
+    public void bindInterceptor(
+        Matcher<? super Class<?>> classMatcher,
+        Matcher<? super Method> methodMatcher,
+        MethodInterceptor... interceptors) {
+      elements.add(new BindInterceptor(getSource(), classMatcher, methodMatcher, interceptors));
+    }
+
+    public void bindScope(Class<? extends Annotation> annotationType, Scope scope) {
+      elements.add(new BindScope(getSource(), annotationType, scope));
+    }
+
+    public void requestInjection(Object... instances) {
+      elements.add(new RequestInjection(getSource(), instances));
+    }
+
+    public void requestStaticInjection(Class<?>... types) {
+      elements.add(new RequestStaticInjection(getSource(), types));
+    }
+
+    public void install(Module module) {
+      if (modules.add(module)) {
+        try {
+          module.configure(this);
+        } catch (RuntimeException e) {
+          addError(e);
+        }
+      }
+    }
+
+    public Stage currentStage() {
+      return stage;
+    }
+
+    public void addError(String message, Object... arguments) {
+      elements.add(new Message(getSource(), String.format(message, arguments)));
+    }
+
+    public void addError(Throwable t) {
+      elements.add(new Message(getSource(), t));
+    }
+
+    public void addError(Message message) {
+      elements.add(message);
+    }
+
+    public <T> ModuleBinding<T>.BindingBuilder bind(Key<T> key) {
+      ModuleBinding<T> moduleBindingCommand = new ModuleBinding<T>(getSource(), key);
+      elements.add(moduleBindingCommand);
+      return moduleBindingCommand.bindingBuilder(RecordingBinder.this);
+    }
+
+    public <T> AnnotatedBindingBuilder<T> bind(TypeLiteral<T> typeLiteral) {
+      return bind(Key.get(typeLiteral));
+    }
+
+    public <T> AnnotatedBindingBuilder<T> bind(Class<T> type) {
+      return bind(Key.get(type));
+    }
+
+    public AnnotatedConstantBindingBuilder bindConstant() {
+      BindConstant bindConstantCommand = new BindConstant(getSource());
+      elements.add(bindConstantCommand);
+      return bindConstantCommand.bindingBuilder(RecordingBinder.this);
+    }
+
+    public <T> Provider<T> getProvider(final Key<T> key) {
+      final GetProvider<T> command = new GetProvider<T>(getSource(), key);
+      elements.add(command);
+      return new Provider<T>() {
+        public T get() {
+          Provider<T> delegate = command.getDelegate();
+          checkState(delegate != null,
+              "This provider cannot be used until the Injector has been created.");
+          return delegate.get();
+        }
+
+        @Override public String toString() {
+          return "Provider<" + key.getTypeLiteral() + ">";
+        }
+      };
+    }
+
+    public <T> Provider<T> getProvider(Class<T> type) {
+      return getProvider(Key.get(type));
+    }
+
+    public void convertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
+        TypeConverter converter) {
+      elements.add(new ConvertToTypes(getSource(), typeMatcher, converter));
+    }
+
+    public Binder withSource(final Object source) {
+      return new RecordingBinder(this, source, null);
+    }
+
+    public Binder skipSources(Class... classesToSkip) {
+      // if a source is specified explicitly, we don't need to skip sources
+      if (source != null) {
+        return this;
+      }
+
+      SourceProvider newSourceProvider = sourceProvider.plusSkippedClasses(classesToSkip);
+      return new RecordingBinder(this, null, newSourceProvider);
+    }
+
+    protected Object getSource() {
+      return sourceProvider != null
+          ? sourceProvider.get()
+          : source;
+    }
+
+    @Override public String toString() {
+      return "Binder";
+    }
+  }
+}
diff --git a/src/com/google/inject/spi/GetProvider.java b/src/com/google/inject/spi/GetProvider.java
new file mode 100644
index 0000000..c6852a8
--- /dev/null
+++ b/src/com/google/inject/spi/GetProvider.java
@@ -0,0 +1,64 @@
+/**
+ * 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.spi;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import com.google.inject.Key;
+import com.google.inject.Provider;
+
+/**
+ * Immutable snapshot of a request for a provider.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class GetProvider<T> implements Element {
+  private final Object source;
+  private final Key<T> key;
+  private Provider<T> delegate;
+
+  GetProvider(Object source, Key<T> key) {
+    this.source = checkNotNull(source, "source");
+    this.key = checkNotNull(key, "key");
+  }
+
+  public Object getSource() {
+    return source;
+  }
+
+  public Key<T> getKey() {
+    return key;
+  }
+
+  public <T> T acceptVisitor(Visitor<T> visitor) {
+    return visitor.visitGetProvider(this);
+  }
+
+  public void initDelegate(Provider<T> delegate) {
+    checkState(this.delegate == null, "delegate already initialized");
+    checkNotNull(delegate, "delegate");
+    this.delegate = delegate;
+  }
+
+  /**
+   * Returns the delegate provider, or {@code null} if it has not yet been initialized. The delegate
+   * will be initialized when this command is replayed, or otherwise used to create an injector.
+   */
+  public Provider<T> getDelegate() {
+    return delegate;
+  }
+}
diff --git a/src/com/google/inject/spi/Message.java b/src/com/google/inject/spi/Message.java
index f83ec65..39dce1d 100644
--- a/src/com/google/inject/spi/Message.java
+++ b/src/com/google/inject/spi/Message.java
@@ -29,7 +29,7 @@
  *
  * @author crazybob@google.com (Bob Lee)
  */
-public final class Message implements Serializable {
+public final class Message implements Serializable, Element {
   private final String source;
   private final String message;
   private final List<InjectionPoint> injectionPoints;
@@ -43,6 +43,11 @@
     this.cause = cause;
   }
 
+  public Message(Object source, Throwable throwable) {
+    this(source, "An exception was caught and reported. Message: " + throwable.getMessage(),
+        ImmutableList.<InjectionPoint>of(), throwable);
+  }
+
   public Message(Object source, String message) {
     this(source, message, ImmutableList.<InjectionPoint>of(), null);
   }
@@ -69,6 +74,10 @@
     return injectionPoints;
   }
 
+  public <T> T acceptVisitor(Visitor<T> visitor) {
+    return visitor.visitMessage(this);
+  }
+
   /**
    * Returns the throwable that caused this message, or {@code null} if this
    * message was not caused by a throwable.
diff --git a/src/com/google/inject/spi/ModuleWriter.java b/src/com/google/inject/spi/ModuleWriter.java
new file mode 100644
index 0000000..0664995
--- /dev/null
+++ b/src/com/google/inject/spi/ModuleWriter.java
@@ -0,0 +1,272 @@
+/**
+ * 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.spi;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.inject.Binder;
+import com.google.inject.Binding;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.Scope;
+import com.google.inject.binder.AnnotatedConstantBindingBuilder;
+import com.google.inject.binder.ConstantBindingBuilder;
+import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.binder.ScopedBindingBuilder;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.util.List;
+import org.aopalliance.intercept.MethodInterceptor;
+
+/**
+ * Converts elements into a Module.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class ModuleWriter {
+
+  /**
+   * Returns a module that executes the specified elements
+   * using this executing visitor.
+   */
+  public final Module create(final Iterable<? extends Element> elements) {
+    return new Module() {
+      public void configure(Binder binder) {
+        apply(binder, elements);
+      }
+    };
+  }
+
+  /**
+   * Replays {@code elements} against {@code binder}.
+   */
+  public final void apply(final Binder binder, Iterable<? extends Element> elements) {
+    checkNotNull(binder, "binder");
+    checkNotNull(elements, "elements");
+
+    Element.Visitor<Void> visitor = new Element.Visitor<Void>() {
+
+      public Void visitMessage(Message message) {
+        writeMessage(binder, message);
+        return null;
+      }
+
+      public Void visitBindInterceptor(BindInterceptor command) {
+        writeBindInterceptor(binder, command);
+        return null;
+      }
+
+      public Void visitBindScope(BindScope command) {
+        writeBindScope(binder, command);
+        return null;
+      }
+
+      public Void visitRequestInjection(RequestInjection command) {
+        writeRequestInjection(binder, command);
+        return null;
+      }
+
+      public Void visitRequestStaticInjection(RequestStaticInjection command) {
+        writeRequestStaticInjection(binder, command);
+        return null;
+      }
+
+      public Void visitBindConstant(BindConstant command) {
+        writeBindConstant(binder, command);
+        return null;
+      }
+
+      public Void visitConvertToTypes(ConvertToTypes command) {
+        writeConvertToTypes(binder, command);
+        return null;
+      }
+
+      public <T> Void visitBinding(Binding<T> command) {
+        writeBind(binder, command);
+        return null;
+      }
+
+      public <T> Void visitGetProvider(GetProvider<T> command) {
+        writeGetProvider(binder, command);
+        return null;
+      }
+    };
+
+    for (Element element : elements) {
+      element.acceptVisitor(visitor);
+    }
+  }
+
+  public void writeMessage(final Binder binder, final Message message) {
+    binder.addError(message);
+  }
+
+  public void writeBindInterceptor(final Binder binder, final BindInterceptor command) {
+    List<MethodInterceptor> interceptors = command.getInterceptors();
+    binder.withSource(command.getSource()).bindInterceptor(
+        command.getClassMatcher(), command.getMethodMatcher(),
+        interceptors.toArray(new MethodInterceptor[interceptors.size()]));
+  }
+
+  public void writeBindScope(final Binder binder, final BindScope command) {
+    binder.withSource(command.getSource()).bindScope(
+        command.getAnnotationType(), command.getScope());
+  }
+
+  public void writeRequestInjection(final Binder binder,
+      final RequestInjection command) {
+    List<Object> objects = command.getInstances();
+    binder.withSource(command.getSource())
+        .requestInjection(objects.toArray());
+  }
+
+  public void writeRequestStaticInjection(final Binder binder,
+      final RequestStaticInjection command) {
+    List<Class> types = command.getTypes();
+    binder.withSource(command.getSource())
+        .requestStaticInjection(types.toArray(new Class[types.size()]));
+  }
+
+  public void writeBindConstant(final Binder binder, final BindConstant command) {
+    AnnotatedConstantBindingBuilder constantBindingBuilder
+        = binder.withSource(command.getSource()).bindConstant();
+
+    Key<Object> key = command.getKey();
+    ConstantBindingBuilder builder = key.getAnnotation() != null
+        ? constantBindingBuilder.annotatedWith(key.getAnnotation())
+        : constantBindingBuilder.annotatedWith(key.getAnnotationType());
+
+    apply(command, builder);
+  }
+
+  public void writeConvertToTypes(final Binder binder, final ConvertToTypes command) {
+    binder.withSource(command.getSource())
+        .convertToTypes(command.getTypeMatcher(), command.getTypeConverter());
+  }
+
+  public <T> void writeBind(final Binder binder, final Binding<T> binding) {
+    LinkedBindingBuilder<T> lbb = binder.withSource(binding.getSource()).bind(binding.getKey());
+
+    ScopedBindingBuilder sbb = applyTarget(binding, lbb);
+    applyScoping(binding, sbb);
+  }
+
+  /**
+   * Execute this target against the linked binding builder.
+   */
+  public <T> ScopedBindingBuilder applyTarget(Binding<T> binding,
+      final LinkedBindingBuilder<T> linkedBindingBuilder) {
+    return binding.acceptTargetVisitor(new com.google.inject.Binding.TargetVisitor<T, ScopedBindingBuilder>() {
+      public ScopedBindingBuilder visitToInstance(T instance) {
+        linkedBindingBuilder.toInstance(instance);
+        return null;
+      }
+
+      public ScopedBindingBuilder visitToProvider(Provider<? extends T> provider) {
+        return linkedBindingBuilder.toProvider(provider);
+      }
+
+      public ScopedBindingBuilder visitToProviderKey(Key<? extends Provider<? extends T>> providerKey) {
+        return linkedBindingBuilder.toProvider(providerKey);
+      }
+
+      public ScopedBindingBuilder visitToKey(Key<? extends T> key) {
+        return linkedBindingBuilder.to(key);
+      }
+
+      public ScopedBindingBuilder visitUntargetted() {
+        return linkedBindingBuilder;
+      }
+
+      public ScopedBindingBuilder visitConstant(T value) {
+        throw new IllegalArgumentException("Non-module element");
+      }
+
+      public ScopedBindingBuilder visitConvertedConstant(T value) {
+        throw new IllegalArgumentException("Non-module element");
+      }
+
+      public ScopedBindingBuilder visitConstructor(Constructor<? extends T> constructor) {
+        throw new IllegalArgumentException("Non-module element");
+      }
+
+      public ScopedBindingBuilder visitProviderBinding(Key<?> provided) {
+        throw new IllegalArgumentException("Non-module element");
+      }
+    });
+  }
+
+  /**
+   * Execute this target against the constant binding builder.
+   */
+  public <T> void apply(BindConstant bindConstant, ConstantBindingBuilder builder) {
+    T t = bindConstant.acceptTargetVisitor(Elements.<T>getInstanceVisitor());
+    Class<?> targetType = t.getClass();
+    if (targetType == String.class) {
+      builder.to((String) t);
+    } else if (targetType == Integer.class) {
+      builder.to((Integer) t);
+    } else if (targetType == Long.class) {
+      builder.to((Long) t);
+    } else if (targetType == Boolean.class) {
+      builder.to((Boolean) t);
+    } else if (targetType == Double.class) {
+      builder.to((Double) t);
+    } else if (targetType == Float.class) {
+      builder.to((Float) t);
+    } else if (targetType == Short.class) {
+      builder.to((Short) t);
+    } else if (targetType == Character.class) {
+      builder.to((Character) t);
+    } else if (targetType == Class.class) {
+      builder.to((Class) t);
+    } else if (Enum.class.isAssignableFrom(targetType)) {
+      builder.to((Enum) t);
+    } else {
+      throw new IllegalArgumentException("Non-constant target " + targetType);
+    }
+  }
+
+  public void applyScoping(Binding<?> binding, final ScopedBindingBuilder scopedBindingBuilder) {
+    binding.acceptScopingVisitor(new com.google.inject.Binding.ScopingVisitor<Void>() {
+      public Void visitEagerSingleton() {
+        scopedBindingBuilder.asEagerSingleton();
+        return null;
+      }
+
+      public Void visitScope(Scope scope) {
+        scopedBindingBuilder.in(scope);
+        return null;
+      }
+
+      public Void visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
+        scopedBindingBuilder.in(scopeAnnotation);
+        return null;
+      }
+
+      public Void visitNoScoping() {
+        // do nothing
+        return null;
+      }
+    });
+  }
+
+  public <T> void writeGetProvider(final Binder binder, final GetProvider<T> command) {
+    Provider<T> provider = binder.withSource(command.getSource()).getProvider(command.getKey());
+    command.initDelegate(provider);
+  }
+}
diff --git a/src/com/google/inject/spi/RequestInjection.java b/src/com/google/inject/spi/RequestInjection.java
new file mode 100644
index 0000000..3eb9866
--- /dev/null
+++ b/src/com/google/inject/spi/RequestInjection.java
@@ -0,0 +1,48 @@
+/**
+ * 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.spi;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+/**
+ * Immutable snapshot of a request for injection.
+ *
+ * @author mikeward@google.com (Mike Ward)
+ */
+public final class RequestInjection implements Element {
+  private Object source;
+  private List<Object> instances;
+
+  public RequestInjection(Object source, Object[] instances) {
+    this.source = checkNotNull(source, "source");
+    this.instances = ImmutableList.of(instances);
+  }
+
+  public Object getSource() {
+    return source;
+  }
+
+  public List<Object> getInstances() {
+    return instances;
+  }
+
+  public <T> T acceptVisitor(Visitor<T> visitor) {
+    return visitor.visitRequestInjection(this);
+  }
+}
diff --git a/src/com/google/inject/spi/RequestStaticInjection.java b/src/com/google/inject/spi/RequestStaticInjection.java
new file mode 100644
index 0000000..5c9b2fa
--- /dev/null
+++ b/src/com/google/inject/spi/RequestStaticInjection.java
@@ -0,0 +1,49 @@
+/**
+ * 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.spi;
+
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+/**
+ * Immutable snapshot of a request for static injection.
+ * 
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class RequestStaticInjection implements Element {
+  private final Object source;
+  private final List<Class> types;
+
+  RequestStaticInjection(Object source, Class[] types) {
+    this.source = checkNotNull(source, "source");
+    this.types = ImmutableList.of(types);
+  }
+
+  public Object getSource() {
+    return source;
+  }
+
+  public List<Class> getTypes() {
+    return types;
+  }
+
+  public <T> T acceptVisitor(Visitor<T> visitor) {
+    return visitor.visitRequestStaticInjection(this);
+  }
+}
diff --git a/src/com/google/inject/spi/BindingVisitor.java b/src/com/google/inject/spi/oldversion/BindingVisitor.java
similarity index 93%
rename from src/com/google/inject/spi/BindingVisitor.java
rename to src/com/google/inject/spi/oldversion/BindingVisitor.java
index e09304a..8e5b0fa 100644
--- a/src/com/google/inject/spi/BindingVisitor.java
+++ b/src/com/google/inject/spi/oldversion/BindingVisitor.java
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.google.inject.spi;
+package com.google.inject.spi.oldversion;
 
 /**
  * Visits bindings. Pass an implementation of {@code BindingVisitor} to
- * {@link com.google.inject.Binding#accept(BindingVisitor)} and the binding
+ * {@link OldVersionBinding#accept(BindingVisitor)} and the binding
  * will call back to the appropriate visitor method for its type.
  *
  * @author crazybob@google.com (Bob Lee)
diff --git a/src/com/google/inject/spi/ClassBinding.java b/src/com/google/inject/spi/oldversion/ClassBinding.java
similarity index 85%
rename from src/com/google/inject/spi/ClassBinding.java
rename to src/com/google/inject/spi/oldversion/ClassBinding.java
index 94528f7..4cf691d 100644
--- a/src/com/google/inject/spi/ClassBinding.java
+++ b/src/com/google/inject/spi/oldversion/ClassBinding.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.google.inject.spi;
+package com.google.inject.spi.oldversion;
 
-import com.google.inject.Binding;
+import com.google.inject.spi.HasInjections;
 
 /**
  * A binding to a concrete, injectable class. Instantiates new instances of the
@@ -26,7 +26,7 @@
  *
  * @author crazybob@google.com (Bob Lee)
  */
-public interface ClassBinding<T> extends Binding<T>, HasInjections {
+public interface ClassBinding<T> extends OldVersionBinding<T>, HasInjections {
 
   /**
    * Gets the class associated with this binding.
diff --git a/src/com/google/inject/spi/ConstantBinding.java b/src/com/google/inject/spi/oldversion/ConstantBinding.java
similarity index 87%
rename from src/com/google/inject/spi/ConstantBinding.java
rename to src/com/google/inject/spi/oldversion/ConstantBinding.java
index c3e9d74..8eaae7c 100644
--- a/src/com/google/inject/spi/ConstantBinding.java
+++ b/src/com/google/inject/spi/oldversion/ConstantBinding.java
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-package com.google.inject.spi;
-
-import com.google.inject.Binding;
+package com.google.inject.spi.oldversion;
 
 /**
  * A binding to a constant.
@@ -25,7 +23,7 @@
  *
  * @author crazybob@google.com (Bob Lee)
  */
-public interface ConstantBinding<T> extends Binding<T> {
+public interface ConstantBinding<T> extends OldVersionBinding<T> {
 
   /**
    * Gets the constant value associated with this binding.
diff --git a/src/com/google/inject/spi/ConvertedConstantBinding.java b/src/com/google/inject/spi/oldversion/ConvertedConstantBinding.java
similarity index 89%
rename from src/com/google/inject/spi/ConvertedConstantBinding.java
rename to src/com/google/inject/spi/oldversion/ConvertedConstantBinding.java
index 5f6a543..e1a6920 100644
--- a/src/com/google/inject/spi/ConvertedConstantBinding.java
+++ b/src/com/google/inject/spi/oldversion/ConvertedConstantBinding.java
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-package com.google.inject.spi;
-
-import com.google.inject.Binding;
+package com.google.inject.spi.oldversion;
 
 /**
  * A binding which was converted from a string contant.
@@ -28,5 +26,5 @@
   /**
    * Gets the binding that we converted to create this binding.
    */
-  Binding<String> getOriginal();
+  OldVersionBinding<String> getOriginal();
 }
diff --git a/src/com/google/inject/spi/InstanceBinding.java b/src/com/google/inject/spi/oldversion/InstanceBinding.java
similarity index 83%
rename from src/com/google/inject/spi/InstanceBinding.java
rename to src/com/google/inject/spi/oldversion/InstanceBinding.java
index 72ec33a..c3733e9 100644
--- a/src/com/google/inject/spi/InstanceBinding.java
+++ b/src/com/google/inject/spi/oldversion/InstanceBinding.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.google.inject.spi;
+package com.google.inject.spi.oldversion;
 
-import com.google.inject.Binding;
+import com.google.inject.spi.HasInjections;
 
 /**
  * A binding to a single instance.
@@ -25,7 +25,7 @@
  *
  * @author crazybob@google.com (Bob Lee)
  */
-public interface InstanceBinding<T> extends Binding<T>, HasInjections {
+public interface InstanceBinding<T> extends OldVersionBinding<T>, HasInjections {
 
   /**
    * Gets the instance associated with this binding.
diff --git a/src/com/google/inject/spi/LinkedBinding.java b/src/com/google/inject/spi/oldversion/LinkedBinding.java
similarity index 83%
rename from src/com/google/inject/spi/LinkedBinding.java
rename to src/com/google/inject/spi/oldversion/LinkedBinding.java
index c52b0cb..598bc6d 100644
--- a/src/com/google/inject/spi/LinkedBinding.java
+++ b/src/com/google/inject/spi/oldversion/LinkedBinding.java
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-package com.google.inject.spi;
-
-import com.google.inject.Binding;
+package com.google.inject.spi.oldversion;
 
 /**
  * A binding that links to another binding.
@@ -25,10 +23,10 @@
  *
  * @author crazybob@google.com (Bob Lee)
  */
-public interface LinkedBinding<T> extends Binding<T> {
+public interface LinkedBinding<T> extends OldVersionBinding<T> {
 
   /**
    * Gets the target of this link.
    */
-  Binding<? extends T> getTarget();
+  OldVersionBinding<? extends T> getTargetBinding();
 }
diff --git a/src/com/google/inject/spi/LinkedProviderBinding.java b/src/com/google/inject/spi/oldversion/LinkedProviderBinding.java
similarity index 80%
rename from src/com/google/inject/spi/LinkedProviderBinding.java
rename to src/com/google/inject/spi/oldversion/LinkedProviderBinding.java
index 6ad6d56..fd676d9 100644
--- a/src/com/google/inject/spi/LinkedProviderBinding.java
+++ b/src/com/google/inject/spi/oldversion/LinkedProviderBinding.java
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.google.inject.spi;
+package com.google.inject.spi.oldversion;
 
-import com.google.inject.Binding;
 import com.google.inject.Provider;
-import com.google.inject.Key;
 
 /**
  * A binding which links to a provider binding which provides instances for
@@ -28,10 +26,10 @@
  *
  * @author crazybob@google.com (Bob Lee)
  */
-public interface LinkedProviderBinding<T> extends Binding<T> {
+public interface LinkedProviderBinding<T> extends OldVersionBinding<T> {
 
   /**
    * Gets the binding for the provider of this binding.
    */
-  Binding<? extends Provider<? extends T>> getTargetProvider();
+  OldVersionBinding<? extends Provider<? extends T>> getTargetProvider();
 }
diff --git a/src/com/google/inject/spi/oldversion/OldVersionBinding.java b/src/com/google/inject/spi/oldversion/OldVersionBinding.java
new file mode 100644
index 0000000..a9bd79c
--- /dev/null
+++ b/src/com/google/inject/spi/oldversion/OldVersionBinding.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright (C) 2006 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.spi.oldversion;
+
+import com.google.inject.Binding;
+import com.google.inject.Scope;
+
+/**
+ * A mapping from a key (type and optional annotation) to a provider of
+ * instances of that type.  This interface is part of the {@link com.google.inject.Injector}
+ * introspection API and is intended primary for use by tools.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public interface OldVersionBinding<T> extends Binding<T> {
+
+  /**
+   * Gets the synthetic binding to this binding's Provider.
+   */
+  ProviderBinding<T> getProviderBinding();
+
+  /**
+   * Returns the scope applied by this binding.
+   */
+  Scope getScope();
+
+  /**
+   * Accepts a binding visitor. Invokes the visitor method specific to this
+   * binding's type.
+   *
+   * @param visitor to call back on
+   */
+  void accept(BindingVisitor<? super T> visitor);
+}
diff --git a/src/com/google/inject/spi/ProviderBinding.java b/src/com/google/inject/spi/oldversion/ProviderBinding.java
similarity index 83%
rename from src/com/google/inject/spi/ProviderBinding.java
rename to src/com/google/inject/spi/oldversion/ProviderBinding.java
index 66096fa..9ab12f4 100644
--- a/src/com/google/inject/spi/ProviderBinding.java
+++ b/src/com/google/inject/spi/oldversion/ProviderBinding.java
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-package com.google.inject.spi;
+package com.google.inject.spi.oldversion;
 
-import com.google.inject.Binding;
 import com.google.inject.Provider;
 
 /**
@@ -25,10 +24,10 @@
  *
  * @author crazybob@google.com (Bob Lee)
  */
-public interface ProviderBinding<T> extends Binding<Provider<T>> {
+public interface ProviderBinding<T> extends OldVersionBinding<Provider<T>> {
 
   /**
    * Gets the binding from which the provider comes.
    */
-  Binding<T> getTarget();
+  OldVersionBinding<T> getTargetBinding();
 }
diff --git a/src/com/google/inject/spi/ProviderInstanceBinding.java b/src/com/google/inject/spi/oldversion/ProviderInstanceBinding.java
similarity index 84%
rename from src/com/google/inject/spi/ProviderInstanceBinding.java
rename to src/com/google/inject/spi/oldversion/ProviderInstanceBinding.java
index b90e1ad..e0ac6b0 100644
--- a/src/com/google/inject/spi/ProviderInstanceBinding.java
+++ b/src/com/google/inject/spi/oldversion/ProviderInstanceBinding.java
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.google.inject.spi;
+package com.google.inject.spi.oldversion;
 
-import com.google.inject.Binding;
 import com.google.inject.Provider;
+import com.google.inject.spi.HasInjections;
 
 /**
  * A binding to a single provider instance.
@@ -26,7 +26,7 @@
  *
  * @author crazybob@google.com (Bob Lee)
  */
-public interface ProviderInstanceBinding<T> extends Binding<T>, HasInjections {
+public interface ProviderInstanceBinding<T> extends OldVersionBinding<T>, HasInjections {
 
   /**
    * Gets the raw (unscoped) provider instance associated with this binding.
diff --git a/test/com/google/inject/AllTests.java b/test/com/google/inject/AllTests.java
index 793bda0..8835284 100644
--- a/test/com/google/inject/AllTests.java
+++ b/test/com/google/inject/AllTests.java
@@ -16,9 +16,6 @@
 
 package com.google.inject;
 
-import com.google.inject.commands.CommandRecorderTest;
-import com.google.inject.commands.CommandReplayerTest;
-import com.google.inject.commands.CommandRewriteTest;
 import com.google.inject.internal.FinalizableReferenceQueueTest;
 import com.google.inject.internal.LineNumbersTest;
 import com.google.inject.internal.ReferenceCacheTest;
@@ -26,6 +23,10 @@
 import com.google.inject.internal.ReferenceMapTestSuite;
 import com.google.inject.internal.UniqueAnnotationsTest;
 import com.google.inject.matcher.MatcherTest;
+import com.google.inject.spi.ElementsTest;
+import com.google.inject.spi.ModuleRewriterTest;
+import com.google.inject.spi.ModuleWriterTest;
+import com.google.inject.spi.SpiBindingsTest;
 import com.google.inject.util.ProvidersTest;
 import com.google.inject.util.TypesTest;
 import com.googlecode.guice.BytecodeGenTest;
@@ -74,9 +75,10 @@
     suite.addTestSuite(TypeLiteralTest.class);
 
     // commands
-    suite.addTestSuite(CommandRecorderTest.class);
-    suite.addTestSuite(CommandReplayerTest.class);
-    suite.addTestSuite(CommandRewriteTest.class);
+    suite.addTestSuite(ElementsTest.class);
+    suite.addTestSuite(ModuleWriterTest.class);
+    suite.addTestSuite(ModuleRewriterTest.class);
+    suite.addTestSuite(SpiBindingsTest.class);
 
     // internal
     suite.addTestSuite(FinalizableReferenceQueueTest.class);
diff --git a/test/com/google/inject/BindingTest.java b/test/com/google/inject/BindingTest.java
index c4fb948..96c2ae8 100644
--- a/test/com/google/inject/BindingTest.java
+++ b/test/com/google/inject/BindingTest.java
@@ -18,16 +18,17 @@
 
 import static com.google.inject.Asserts.assertContains;
 import com.google.inject.name.Names;
-import com.google.inject.spi.BindingVisitor;
-import com.google.inject.spi.ClassBinding;
-import com.google.inject.spi.ConstantBinding;
-import com.google.inject.spi.ConvertedConstantBinding;
 import com.google.inject.spi.InjectionPoint;
-import com.google.inject.spi.InstanceBinding;
-import com.google.inject.spi.LinkedBinding;
-import com.google.inject.spi.LinkedProviderBinding;
-import com.google.inject.spi.ProviderBinding;
-import com.google.inject.spi.ProviderInstanceBinding;
+import com.google.inject.spi.oldversion.BindingVisitor;
+import com.google.inject.spi.oldversion.ClassBinding;
+import com.google.inject.spi.oldversion.ConstantBinding;
+import com.google.inject.spi.oldversion.ConvertedConstantBinding;
+import com.google.inject.spi.oldversion.InstanceBinding;
+import com.google.inject.spi.oldversion.LinkedBinding;
+import com.google.inject.spi.oldversion.LinkedProviderBinding;
+import com.google.inject.spi.oldversion.OldVersionBinding;
+import com.google.inject.spi.oldversion.ProviderBinding;
+import com.google.inject.spi.oldversion.ProviderInstanceBinding;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -66,11 +67,11 @@
 
   public void testProviderBinding() {
     Injector injector = Guice.createInjector();
-    Binding<Bob> bobBinding = injector.getBinding(Bob.class);
+    OldVersionBinding<Bob> bobBinding = (OldVersionBinding) injector.getBinding(Bob.class);
     assertTrue(bobBinding.getProvider().get() instanceof Bob);
-    Binding<Provider<Bob>> bobProviderBinding = bobBinding.getProviderBinding();
+    OldVersionBinding<Provider<Bob>> bobProviderBinding = bobBinding.getProviderBinding();
     assertTrue(bobProviderBinding.getProvider().get().get() instanceof Bob);
-    Binding<Provider<Provider<Bob>>> bobProviderProviderBinding
+    OldVersionBinding<Provider<Provider<Bob>>> bobProviderProviderBinding
         = bobProviderBinding.getProviderBinding();
     assertTrue(bobProviderProviderBinding.getProvider().get().get().get()
         instanceof Bob);
@@ -84,7 +85,7 @@
     Injector injector = Guice.createInjector(new MyModule());
 
     for (Binding<?> binding : injector.getBindings().values()) {
-      binding.accept(myVisitor);
+      ((OldVersionBinding) binding).accept(myVisitor);
     }
 
     myVisitor.verify();
@@ -138,7 +139,7 @@
     public void visit(LinkedBinding<?> linkedBinding) {
       linkedVisited = true;
       assertEquals(Runnable.class,
-          linkedBinding.getTarget().getKey().getTypeLiteral().getType());
+          linkedBinding.getTargetBinding().getKey().getTypeLiteral().getType());
       assertEquals(Scopes.SINGLETON, linkedBinding.getScope());
     }
 
diff --git a/test/com/google/inject/LoggerInjectionTest.java b/test/com/google/inject/LoggerInjectionTest.java
index af7358a..cd71657 100644
--- a/test/com/google/inject/LoggerInjectionTest.java
+++ b/test/com/google/inject/LoggerInjectionTest.java
@@ -2,10 +2,9 @@
 
 import static com.google.inject.Asserts.assertContains;
 import com.google.inject.name.Names;
-import com.google.inject.spi.ProviderInstanceBinding;
-import junit.framework.TestCase;
-
+import com.google.inject.spi.oldversion.ProviderInstanceBinding;
 import java.util.logging.Logger;
+import junit.framework.TestCase;
 
 /**
  * Test built-in injection of loggers.
diff --git a/test/com/google/inject/spi/ElementsTest.java b/test/com/google/inject/spi/ElementsTest.java
new file mode 100644
index 0000000..db5b34d
--- /dev/null
+++ b/test/com/google/inject/spi/ElementsTest.java
@@ -0,0 +1,865 @@
+/**
+ * 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.spi;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Asserts;
+import com.google.inject.Binding;
+import com.google.inject.BindingAnnotation;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.Scope;
+import com.google.inject.Scopes;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.binder.AnnotatedBindingBuilder;
+import com.google.inject.binder.AnnotatedConstantBindingBuilder;
+import com.google.inject.binder.ConstantBindingBuilder;
+import com.google.inject.binder.ScopedBindingBuilder;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.matcher.Matchers;
+import com.google.inject.name.Names;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicInteger;
+import junit.framework.TestCase;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class ElementsTest extends TestCase {
+
+  // Binder fidelity tests
+
+  public void testAddMessageErrorCommand() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            addError("Message %s %d %s", "A", 5, "C");
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitMessage(Message command) {
+            assertEquals("Message A 5 C", command.getMessage());
+            assertNull(command.getCause());
+            assertTrue(command.getInjectionPoints().isEmpty());
+            Asserts.assertContains(command.getSource(), "ElementsTest.java");
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testAddThrowableErrorCommand() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            addError(new Exception("A"));
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitMessage(Message command) {
+            assertEquals("A", command.getCause().getMessage());
+            assertEquals(command.getMessage(),
+                "An exception was caught and reported. Message: A");
+            assertTrue(command.getInjectionPoints().isEmpty());
+            Asserts.assertContains(command.getSource(), "ElementsTest.java");
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testErrorsAddedWhenExceptionsAreThrown() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            install(new AbstractModule() {
+              protected void configure() {
+                throw new RuntimeException("Throwing RuntimeException in AbstractModule.configure().");
+              }
+            });
+
+            addError("Code after the exception still gets executed");
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitMessage(Message command) {
+            assertEquals("Throwing RuntimeException in AbstractModule.configure().",
+                command.getCause().getMessage());
+            assertTrue(command.getInjectionPoints().isEmpty());
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitMessage(Message command) {
+            assertEquals("Code after the exception still gets executed",
+                command.getMessage());
+            return null;
+          }
+        }
+    );
+  }
+
+  private <T> T getInstance(Binding<T> binding) {
+    return binding.acceptTargetVisitor(Elements.<T>getInstanceVisitor());
+  }
+
+  private <T> T getInstance(BindConstant bindConstant) {
+    return bindConstant.acceptTargetVisitor(Elements.<T>getInstanceVisitor());
+  }
+
+  public void testBindConstantAnnotations() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            bindConstant().annotatedWith(SampleAnnotation.class).to("A");
+            bindConstant().annotatedWith(Names.named("Bee")).to("B");
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            assertEquals(Key.get(String.class, SampleAnnotation.class), command.getKey());
+            assertEquals("A", getInstance(command));
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            assertEquals(Key.get(String.class, Names.named("Bee")), command.getKey());
+            assertEquals("B", getInstance(command));
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testBindConstantTypes() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            bindConstant().annotatedWith(Names.named("String")).to("A");
+            bindConstant().annotatedWith(Names.named("int")).to(2);
+            bindConstant().annotatedWith(Names.named("long")).to(3L);
+            bindConstant().annotatedWith(Names.named("boolean")).to(false);
+            bindConstant().annotatedWith(Names.named("double")).to(5.0d);
+            bindConstant().annotatedWith(Names.named("float")).to(6.0f);
+            bindConstant().annotatedWith(Names.named("short")).to((short) 7);
+            bindConstant().annotatedWith(Names.named("char")).to('h');
+            bindConstant().annotatedWith(Names.named("Class")).to(Iterator.class);
+            bindConstant().annotatedWith(Names.named("Enum")).to(CoinSide.TAILS);
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            assertEquals(Key.get(String.class, Names.named("String")), command.getKey());
+            assertEquals("A", getInstance(command));
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            assertEquals(Key.get(Integer.class, Names.named("int")), command.getKey());
+            assertEquals(2, getInstance(command));
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            assertEquals(Key.get(Long.class, Names.named("long")), command.getKey());
+            assertEquals(3L, getInstance(command));
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            assertEquals(Key.get(Boolean.class, Names.named("boolean")), command.getKey());
+            assertEquals(false, getInstance(command));
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            assertEquals(Key.get(Double.class, Names.named("double")), command.getKey());
+            assertEquals(5.0d, getInstance(command));
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            assertEquals(Key.get(Float.class, Names.named("float")), command.getKey());
+            assertEquals(6.0f, getInstance(command));
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            assertEquals(Key.get(Short.class, Names.named("short")), command.getKey());
+            assertEquals((short) 7, getInstance(command));
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            assertEquals(Key.get(Character.class, Names.named("char")), command.getKey());
+            assertEquals('h', getInstance(command));
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            assertEquals(Key.get(Class.class, Names.named("Class")), command.getKey());
+            assertEquals(Iterator.class, getInstance(command));
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            assertEquals(Key.get(CoinSide.class, Names.named("Enum")), command.getKey());
+            assertEquals(CoinSide.TAILS, getInstance(command));
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testBindKeysNoAnnotations() {
+    FailingElementVisitor keyChecker = new FailingElementVisitor() {
+      @Override public <T> Void visitBinding(Binding<T> command) {
+        assertEquals(Key.get(String.class), command.getKey());
+        return null;
+      }
+    };
+
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            bind(String.class).toInstance("A");
+            bind(new TypeLiteral<String>() {}).toInstance("B");
+            bind(Key.get(String.class)).toInstance("C");
+          }
+        },
+        keyChecker,
+        keyChecker,
+        keyChecker
+    );
+  }
+
+  public void testBindKeysWithAnnotationType() {
+    FailingElementVisitor annotationChecker = new FailingElementVisitor() {
+      @Override public <T> Void visitBinding(Binding<T> command) {
+        assertEquals(Key.get(String.class, SampleAnnotation.class), command.getKey());
+        return null;
+      }
+    };
+
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            bind(String.class).annotatedWith(SampleAnnotation.class).toInstance("A");
+            bind(new TypeLiteral<String>() {}).annotatedWith(SampleAnnotation.class).toInstance("B");
+          }
+        },
+        annotationChecker,
+        annotationChecker
+    );
+  }
+
+  public void testBindKeysWithAnnotationInstance() {
+    FailingElementVisitor annotationChecker = new FailingElementVisitor() {
+      @Override public <T> Void visitBinding(Binding<T> command) {
+        assertEquals(Key.get(String.class, Names.named("a")), command.getKey());
+        return null;
+      }
+    };
+
+
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            bind(String.class).annotatedWith(Names.named("a")).toInstance("B");
+            bind(new TypeLiteral<String>() {}).annotatedWith(Names.named("a")).toInstance("C");
+          }
+        },
+        annotationChecker,
+        annotationChecker
+    );
+  }
+
+  public void testBindToProvider() {
+    final Provider<String> aProvider = new Provider<String>() {
+      public String get() {
+        return "A";
+      }
+    };
+
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            bind(String.class).toProvider(aProvider);
+            bind(List.class).toProvider(ListProvider.class);
+            bind(Collection.class).toProvider(Key.get(ListProvider.class));
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(String.class), command.getKey());
+            command.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+              public Void visitToProvider(Provider<? extends T> provider) {
+                assertSame(aProvider, provider);
+                return null;
+              }
+            });
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(List.class), command.getKey());
+            command.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+              public Void visitToProviderKey(Key<? extends Provider<? extends T>> providerKey) {
+                assertEquals(Key.get(ListProvider.class), providerKey);
+                return null;
+              }
+            });
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(Collection.class), command.getKey());
+            command.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+              public Void visitToProviderKey(Key<? extends Provider<? extends T>> providerKey) {
+                assertEquals(Key.get(ListProvider.class), providerKey);
+                return null;
+              }
+            });
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testBindToLinkedBinding() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            bind(List.class).to(ArrayList.class);
+            bind(Map.class).to(new TypeLiteral<HashMap<Integer, String>>() {});
+            bind(Set.class).to(Key.get(TreeSet.class, SampleAnnotation.class));
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(List.class), command.getKey());
+            command.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+              public Void visitToKey(Key<? extends T> key) {
+                assertEquals(Key.get(ArrayList.class), key);
+                return null;
+              }
+            });
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(Map.class), command.getKey());
+            command.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+              public Void visitToKey(Key<? extends T> key) {
+                assertEquals(Key.get(new TypeLiteral<HashMap<Integer, String>>() {}), key);
+                return null;
+              }
+            });
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(Set.class), command.getKey());
+            command.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+              public Void visitToKey(Key<? extends T> key) {
+                assertEquals(Key.get(TreeSet.class, SampleAnnotation.class), key);
+                return null;
+              }
+            });
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testBindToInstance() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            bind(String.class).toInstance("A");
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(String.class), command.getKey());
+            assertEquals("A", getInstance(command));
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testBindInScopes() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            bind(String.class);
+            bind(List.class).to(ArrayList.class).in(Scopes.SINGLETON);
+            bind(Map.class).to(HashMap.class).in(Singleton.class);
+            bind(Set.class).to(TreeSet.class).asEagerSingleton();
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(String.class), command.getKey());
+            command.acceptScopingVisitor(new FailingBindScopingVisitor() {
+              @Override public Void visitNoScoping() {
+                return null;
+              }
+            });
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(List.class), command.getKey());
+            command.acceptScopingVisitor(new FailingBindScopingVisitor() {
+              @Override public Void visitScope(Scope scope) {
+                assertEquals(Scopes.SINGLETON, scope);
+                return null;
+              }
+            });
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(Map.class), command.getKey());
+            command.acceptScopingVisitor(new FailingBindScopingVisitor() {
+              @Override public Void visitScopeAnnotation(Class<? extends Annotation> annotation) {
+                assertEquals(Singleton.class, annotation);
+                return null;
+              }
+            });
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(Set.class), command.getKey());
+            command.acceptScopingVisitor(new FailingBindScopingVisitor() {
+              public Void visitEagerSingleton() {
+                return null;
+              }
+            });
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testBindIntercepor() {
+    final Matcher<Class> classMatcher = Matchers.subclassesOf(List.class);
+    final Matcher<Object> methodMatcher = Matchers.any();
+    final MethodInterceptor methodInterceptor = new MethodInterceptor() {
+      public Object invoke(MethodInvocation methodInvocation) {
+        return null;
+      }
+    };
+
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            bindInterceptor(classMatcher, methodMatcher, methodInterceptor);
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindInterceptor(BindInterceptor command) {
+            assertSame(classMatcher, command.getClassMatcher());
+            assertSame(methodMatcher, command.getMethodMatcher());
+            assertEquals(Arrays.asList(methodInterceptor), command.getInterceptors());
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testBindScope() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            bindScope(SampleAnnotation.class, Scopes.NO_SCOPE);
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindScope(BindScope command) {
+            assertSame(SampleAnnotation.class, command.getAnnotationType());
+            assertSame(Scopes.NO_SCOPE, command.getScope());
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testConvertToTypes() {
+    final TypeConverter typeConverter = new TypeConverter() {
+      public Object convert(String value, TypeLiteral<?> toType) {
+        return value;
+      }
+    };
+
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            convertToTypes(Matchers.any(), typeConverter);
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitConvertToTypes(ConvertToTypes command) {
+            assertSame(typeConverter, command.getTypeConverter());
+            assertSame(Matchers.any(), command.getTypeMatcher());
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testGetProvider() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            Provider<String> keyGetProvider = getProvider(Key.get(String.class, SampleAnnotation.class));
+            try {
+              keyGetProvider.get();
+            } catch (IllegalStateException e) {
+              assertEquals("This provider cannot be used until the Injector has been created.",
+                  e.getMessage());
+            }
+
+            Provider<String> typeGetProvider = getProvider(String.class);
+            try {
+              typeGetProvider.get();
+            } catch (IllegalStateException e) {
+              assertEquals("This provider cannot be used until the Injector has been created.",
+                  e.getMessage());
+            }
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitGetProvider(GetProvider command) {
+            assertEquals(Key.get(String.class, SampleAnnotation.class), command.getKey());
+            assertNull(command.getDelegate());
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitGetProvider(GetProvider command) {
+            assertEquals(Key.get(String.class), command.getKey());
+            assertNull(command.getDelegate());
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testRequestInjection() {
+    final Object firstObject = new Object();
+    final Object secondObject = new Object();
+
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            requestInjection(firstObject, secondObject);
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitRequestInjection(RequestInjection command) {
+            assertEquals(Arrays.asList(firstObject, secondObject), command.getInstances());
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testRequestStaticInjection() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            requestStaticInjection(ArrayList.class);
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitRequestStaticInjection(RequestStaticInjection command) {
+            assertEquals(Arrays.asList(ArrayList.class), command.getTypes());
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testBindWithMultipleAnnotationsAddsError() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            AnnotatedBindingBuilder<String> abb = bind(String.class);
+            abb.annotatedWith(SampleAnnotation.class);
+            abb.annotatedWith(Names.named("A"));
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitMessage(Message command) {
+            assertEquals("More than one annotation is specified for this binding.",
+                command.getMessage());
+            assertNull(command.getCause());
+            assertTrue(command.getInjectionPoints().isEmpty());
+            Asserts.assertContains(command.getSource(), "ElementsTest.java");
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testBindWithMultipleTargetsAddsError() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            AnnotatedBindingBuilder<String> abb = bind(String.class);
+            abb.toInstance("A");
+            abb.toInstance("B");
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitMessage(Message command) {
+            assertEquals("Implementation is set more than once.", command.getMessage());
+            assertNull(command.getCause());
+            assertTrue(command.getInjectionPoints().isEmpty());
+            Asserts.assertContains(command.getSource(), "ElementsTest.java");
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testBindWithMultipleScopesAddsError() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            ScopedBindingBuilder sbb = bind(List.class).to(ArrayList.class);
+            sbb.in(Scopes.NO_SCOPE);
+            sbb.asEagerSingleton();
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitMessage(Message command) {
+            assertEquals("Scope is set more than once.", command.getMessage());
+            assertNull(command.getCause());
+            assertTrue(command.getInjectionPoints().isEmpty());
+            Asserts.assertContains(command.getSource(), "ElementsTest.java");
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testBindConstantWithMultipleAnnotationsAddsError() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            AnnotatedConstantBindingBuilder cbb = bindConstant();
+            cbb.annotatedWith(SampleAnnotation.class).to("A");
+            cbb.annotatedWith(Names.named("A"));
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitMessage(Message command) {
+            assertEquals("More than one annotation is specified for this binding.",
+                command.getMessage());
+            assertNull(command.getCause());
+            assertTrue(command.getInjectionPoints().isEmpty());
+            Asserts.assertContains(command.getSource(), "ElementsTest.java");
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testBindConstantWithMultipleTargetsAddsError() {
+    checkModule(
+        new AbstractModule() {
+          protected void configure() {
+            ConstantBindingBuilder cbb = bindConstant().annotatedWith(SampleAnnotation.class);
+            cbb.to("A");
+            cbb.to("B");
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitBindConstant(BindConstant command) {
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public Void visitMessage(Message message) {
+            assertEquals("Constant value is set more than once.", message.getMessage());
+            assertNull(message.getCause());
+            assertTrue(message.getInjectionPoints().isEmpty());
+            Asserts.assertContains(message.getSource(), "ElementsTest.java");
+            return null;
+          }
+        }
+    );
+  }
+
+  // Business logic tests
+
+  public void testModulesAreInstalledAtMostOnce() {
+    final AtomicInteger aConfigureCount = new AtomicInteger(0);
+    final Module a = new AbstractModule() {
+      public void configure() {
+        aConfigureCount.incrementAndGet();
+      }
+    };
+
+    Elements.getElements(a, a);
+    assertEquals(1, aConfigureCount.get());
+
+    aConfigureCount.set(0);
+    Module b = new AbstractModule() {
+      protected void configure() {
+        install(a);
+        install(a);
+      }
+    };
+
+    Elements.getElements(b);
+    assertEquals(1, aConfigureCount.get());
+  }
+
+
+  /**
+   * Ensures the module performs the commands consistent with {@code visitors}.
+   */
+  protected void checkModule(Module module, Element.Visitor<?>... visitors) {
+    List<Element> elements = Elements.getElements(module);
+
+    assertEquals(elements.size(), visitors.length);
+
+    for (int i = 0; i < visitors.length; i++) {
+      Element.Visitor<?> visitor = visitors[i];
+      Element element = elements.get(i);
+      Asserts.assertContains(element.getSource().toString(), "ElementsTest.java");
+      element.acceptVisitor(visitor);
+    }
+  }
+
+  private static class ListProvider implements Provider<List> {
+    public List get() {
+      return new ArrayList();
+    }
+  }
+
+  @Retention(RUNTIME)
+  @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
+  @BindingAnnotation
+  public @interface SampleAnnotation { }
+
+  public enum CoinSide { HEADS, TAILS }
+}
diff --git a/test/com/google/inject/spi/FailingBindScopingVisitor.java b/test/com/google/inject/spi/FailingBindScopingVisitor.java
new file mode 100644
index 0000000..3a4e4be
--- /dev/null
+++ b/test/com/google/inject/spi/FailingBindScopingVisitor.java
@@ -0,0 +1,40 @@
+/**
+ * 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.spi;
+
+import com.google.inject.Scope;
+import java.lang.annotation.Annotation;
+import junit.framework.AssertionFailedError;
+
+class FailingBindScopingVisitor implements com.google.inject.Binding.ScopingVisitor<Void> {
+
+  public Void visitEagerSingleton() {
+    throw new AssertionFailedError();
+  }
+
+  public Void visitScope(Scope scope) {
+    throw new AssertionFailedError();
+  }
+
+  public Void visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
+    throw new AssertionFailedError();
+  }
+
+  public Void visitNoScoping() {
+    throw new AssertionFailedError();
+  }
+}
\ No newline at end of file
diff --git a/src/com/google/inject/spi/ConvertedConstantBinding.java b/test/com/google/inject/spi/FailingElementVisitor.java
similarity index 62%
copy from src/com/google/inject/spi/ConvertedConstantBinding.java
copy to test/com/google/inject/spi/FailingElementVisitor.java
index 5f6a543..f91a234 100644
--- a/src/com/google/inject/spi/ConvertedConstantBinding.java
+++ b/test/com/google/inject/spi/FailingElementVisitor.java
@@ -1,5 +1,5 @@
-/*
- * Copyright (C) 2007 Google Inc.
+/**
+ * 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.
@@ -16,17 +16,10 @@
 
 package com.google.inject.spi;
 
-import com.google.inject.Binding;
+import junit.framework.AssertionFailedError;
 
-/**
- * A binding which was converted from a string contant.
- *
- * @author crazybob@google.com (Bob Lee)
- */
-public interface ConvertedConstantBinding<T> extends ConstantBinding<T> {
-
-  /**
-   * Gets the binding that we converted to create this binding.
-   */
-  Binding<String> getOriginal();
+class FailingElementVisitor extends DefaultElementVisitor<Void> {
+  @Override protected Void visitElement(Element element) {
+    throw new AssertionFailedError();
+  }
 }
diff --git a/src/com/google/inject/spi/ConvertedConstantBinding.java b/test/com/google/inject/spi/FailingTargetVisitor.java
similarity index 62%
copy from src/com/google/inject/spi/ConvertedConstantBinding.java
copy to test/com/google/inject/spi/FailingTargetVisitor.java
index 5f6a543..2aad051 100644
--- a/src/com/google/inject/spi/ConvertedConstantBinding.java
+++ b/test/com/google/inject/spi/FailingTargetVisitor.java
@@ -1,5 +1,5 @@
-/*
- * Copyright (C) 2007 Google Inc.
+/**
+ * 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.
@@ -16,17 +16,10 @@
 
 package com.google.inject.spi;
 
-import com.google.inject.Binding;
+import junit.framework.AssertionFailedError;
 
-/**
- * A binding which was converted from a string contant.
- *
- * @author crazybob@google.com (Bob Lee)
- */
-public interface ConvertedConstantBinding<T> extends ConstantBinding<T> {
-
-  /**
-   * Gets the binding that we converted to create this binding.
-   */
-  Binding<String> getOriginal();
+public class FailingTargetVisitor<T> extends DefaultBindTargetVisitor<T, Void> {
+  @Override protected Void visitTarget() {
+    throw new AssertionFailedError();
+  }
 }
diff --git a/test/com/google/inject/spi/ModuleRewriterTest.java b/test/com/google/inject/spi/ModuleRewriterTest.java
new file mode 100644
index 0000000..c592470
--- /dev/null
+++ b/test/com/google/inject/spi/ModuleRewriterTest.java
@@ -0,0 +1,102 @@
+/**
+ * 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.spi;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+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.Module;
+import com.google.inject.Provider;
+import com.google.inject.name.Names;
+import java.util.List;
+import junit.framework.TestCase;
+
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class ModuleRewriterTest extends TestCase {
+
+  public void testRewriteBindings() {
+    // create a module the binds String.class and CharSequence.class
+    Module module = new AbstractModule() {
+      protected void configure() {
+        bind(String.class).toInstance("Pizza");
+        bind(CharSequence.class).toInstance("Wine");
+      }
+    };
+
+    // record the elements from that module
+    List<Element> elements = Elements.getElements(module);
+
+    // create a rewriter that rewrites the binding to 'Wine' with a binding to 'Beer'
+    ModuleWriter rewriter = new ModuleWriter() {
+      @Override public <T> void writeBind(Binder binder, Binding<T> binding) {
+        T target = binding.acceptTargetVisitor(Elements.<T>getInstanceVisitor());
+        if ("Wine".equals(target)) {
+          binder.bind(CharSequence.class).toInstance("Beer");
+        } else {
+          super.writeBind(binder, binding);
+        }
+      }
+    };
+
+    // create a module from the original list of elements and the rewriter
+    Module rewrittenModule = rewriter.create(elements);
+
+    // it all works
+    Injector injector = Guice.createInjector(rewrittenModule);
+    assertEquals("Pizza", injector.getInstance(String.class));
+    assertEquals("Beer", injector.getInstance(CharSequence.class));
+  }
+
+  public void testGetProviderAvailableAtInjectMembersTime() {
+    Module module = new AbstractModule() {
+      public void configure() {
+        final Provider<String> stringProvider = getProvider(String.class);
+
+        bind(String.class).annotatedWith(Names.named("2")).toProvider(new Provider<String>() {
+          private String value;
+
+          @Inject void initialize() {
+            value = stringProvider.get();
+          }
+
+          public String get() {
+            return value;
+          }
+        });
+
+        bind(String.class).toInstance("A");
+      }
+    };
+
+    // the module works fine normally
+    Injector injector = Guice.createInjector(module);
+    assertEquals("A", injector.getInstance(Key.get(String.class, Names.named("2"))));
+
+    // and it should also work fine if we rewrite it
+    List<Element> elements = Elements.getElements(module);
+    Module replayed = new ModuleWriter().create(elements);
+    Injector replayedInjector = Guice.createInjector(replayed);
+    assertEquals("A", replayedInjector.getInstance(Key.get(String.class, Names.named("2"))));
+  }
+}
diff --git a/test/com/google/inject/spi/ModuleWriterTest.java b/test/com/google/inject/spi/ModuleWriterTest.java
new file mode 100644
index 0000000..f97819c
--- /dev/null
+++ b/test/com/google/inject/spi/ModuleWriterTest.java
@@ -0,0 +1,44 @@
+/**
+ * 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.spi;
+
+import com.google.inject.Module;
+import java.util.List;
+
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class ModuleWriterTest extends ElementsTest {
+
+  protected void checkModule(Module module, Element.Visitor<?>... visitors) {
+    // get some elements to apply
+    List<Element> elements = Elements.getElements(module);
+
+    // apply the recorded elements, and record them again!
+    List<Element> rewrittenElements
+        = Elements.getElements(new ModuleWriter().create(elements));
+
+    // verify that the replayed elements are as expected
+    assertEquals(rewrittenElements.size(), visitors.length);
+    for (int i = 0; i < visitors.length; i++) {
+      Element.Visitor<?> visitor = visitors[i];
+      Element element = rewrittenElements.get(i);
+      element.acceptVisitor(visitor);
+    }
+  }
+}
diff --git a/test/com/google/inject/spi/SpiBindingsTest.java b/test/com/google/inject/spi/SpiBindingsTest.java
new file mode 100644
index 0000000..2349fb8
--- /dev/null
+++ b/test/com/google/inject/spi/SpiBindingsTest.java
@@ -0,0 +1,370 @@
+/**
+ * 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.spi;
+
+import com.google.common.base.Nullable;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+import com.google.inject.AbstractModule;
+import static com.google.inject.Asserts.assertContains;
+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.Module;
+import com.google.inject.Provider;
+import com.google.inject.Scope;
+import com.google.inject.Scopes;
+import com.google.inject.Singleton;
+import com.google.inject.Stage;
+import com.google.inject.name.Names;
+import java.lang.reflect.Constructor;
+import java.util.List;
+import java.util.logging.Logger;
+import junit.framework.TestCase;
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class SpiBindingsTest extends TestCase {
+
+  public void testBindConstant() {
+    checkInjector(
+        new AbstractModule() {
+          protected void configure() {
+            bindConstant().annotatedWith(Names.named("one")).to(1);
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> binding) {
+            assertEquals(Key.get(Integer.class, Names.named("one")), binding.getKey());
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testToInstanceBinding() {
+    checkInjector(
+        new AbstractModule() {
+          protected void configure() {
+            bind(String.class).toInstance("A");
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertContains(command.getSource().toString(), "SpiBindingsTest.java");
+            assertEquals(Key.get(String.class), command.getKey());
+            command.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+              @Override public Void visitToInstance(T instance) {
+                assertEquals("A", instance);
+                return null;
+              }
+            });
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testToProviderBinding() {
+    final Provider<String> stringProvider = new StringProvider();
+
+    checkInjector(
+        new AbstractModule() {
+          protected void configure() {
+            bind(String.class).toProvider(stringProvider);
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertContains(command.getSource().toString(), "SpiBindingsTest.java");
+            assertEquals(Key.get(String.class), command.getKey());
+            command.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+              @Override public Void visitToProvider(Provider<? extends T> provider) {
+                assertSame(stringProvider, provider);
+                return null;
+              }
+            });
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testToProviderKeyBinding() {
+    checkInjector(
+        new AbstractModule() {
+          protected void configure() {
+            bind(String.class).toProvider(StringProvider.class);
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertContains(command.getSource().toString(), "SpiBindingsTest.java");
+            assertEquals(Key.get(String.class), command.getKey());
+            command.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+              @Override public Void visitToProviderKey(Key<? extends Provider<? extends T>> key) {
+                assertEquals(Key.get(StringProvider.class), key);
+                return null;
+              }
+            });
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testToKeyBinding() {
+    final Key<String> aKey = Key.get(String.class, Names.named("a"));
+    final Key<String> bKey = Key.get(String.class, Names.named("b"));
+
+    checkInjector(
+        new AbstractModule() {
+          protected void configure() {
+            bind(aKey).to(bKey);
+            bind(bKey).toInstance("B");
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertContains(command.getSource().toString(), "SpiBindingsTest.java");
+            assertEquals(aKey, command.getKey());
+            command.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+              @Override public Void visitToKey(Key<? extends T> key) {
+                assertEquals(bKey, key);
+                return null;
+              }
+            });
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(bKey, command.getKey());
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testToConstructorBinding() {
+    checkInjector(
+        new AbstractModule() {
+          protected void configure() {
+            bind(D.class);
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertContains(command.getSource().toString(), "SpiBindingsTest.java");
+            assertEquals(Key.get(D.class), command.getKey());
+            command.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+              @Override public Void visitConstructor(Constructor<? extends T> constructor) {
+                Constructor<?> expected = D.class.getDeclaredConstructors()[0];
+                assertEquals(expected, constructor);
+                return null;
+              }
+            });
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testConstantBinding() {
+    checkInjector(
+        new AbstractModule() {
+          protected void configure() {
+            bindConstant().annotatedWith(Names.named("one")).to(1);
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertContains(command.getSource().toString(), "SpiBindingsTest.java");
+            assertEquals(Key.get(Integer.class, Names.named("one")), command.getKey());
+            command.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+              @Override public Void visitConstant(T value) {
+                assertEquals((Integer) 1, value);
+                return null;
+              }
+            });
+            return null;
+          }
+        }
+    );
+  }
+
+  public void testConvertedConstantBinding() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        bindConstant().annotatedWith(Names.named("one")).to("1");
+      }
+    });
+
+    Binding<Integer> binding = injector.getBinding(Key.get(Integer.class, Names.named("one")));
+    assertEquals(Key.get(Integer.class, Names.named("one")), binding.getKey());
+    assertContains(binding.getSource().toString(), "SpiBindingsTest.java");
+    binding.acceptTargetVisitor(new FailingTargetVisitor<Integer>() {
+      @Override public Void visitConvertedConstant(Integer value) {
+        assertEquals((Integer) 1, value);
+        return null;
+      }
+    });
+  }
+
+  public void testProviderBinding() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        bind(String.class).toInstance("A");
+      }
+    });
+
+    Key<Provider<String>> providerOfStringKey = new Key<Provider<String>>() {};
+    Binding<Provider<String>> binding = injector.getBinding(providerOfStringKey);
+    assertEquals(providerOfStringKey, binding.getKey());
+    assertContains(binding.getSource().toString(), "SpiBindingsTest.java");
+    binding.acceptTargetVisitor(new FailingTargetVisitor<Provider<String>>() {
+      @Override public Void visitProviderBinding(Key<?> provided) {
+        assertEquals(Key.get(String.class), provided);
+        return null;
+      }
+    });
+  }
+
+  public void testScopes() {
+    checkInjector(
+        new AbstractModule() {
+          protected void configure() {
+            bind(String.class).annotatedWith(Names.named("a"))
+                .toProvider(StringProvider.class).in(Singleton.class);
+            bind(String.class).annotatedWith(Names.named("b"))
+                .toProvider(StringProvider.class).in(Scopes.SINGLETON);
+            bind(String.class).annotatedWith(Names.named("c"))
+                .toProvider(StringProvider.class).asEagerSingleton();
+            bind(String.class).annotatedWith(Names.named("d"))
+                .toProvider(StringProvider.class);
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(String.class, Names.named("a")), command.getKey());
+            command.acceptScopingVisitor(new FailingBindScopingVisitor() {
+              @Override public Void visitScope(Scope scope) {
+                // even though we bound with an annotation, the injector always uses instances
+                assertSame(Scopes.SINGLETON, scope);
+                return null;
+              }
+            });
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(String.class, Names.named("b")), command.getKey());
+            command.acceptScopingVisitor(new FailingBindScopingVisitor() {
+              @Override public Void visitScope(Scope scope) {
+                assertSame(Scopes.SINGLETON, scope);
+                return null;
+              }
+            });
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(String.class, Names.named("c")), command.getKey());
+            command.acceptScopingVisitor(new FailingBindScopingVisitor() {
+              @Override public Void visitEagerSingleton() {
+                return null;
+              }
+            });
+            return null;
+          }
+        },
+
+        new FailingElementVisitor() {
+          @Override public <T> Void visitBinding(Binding<T> command) {
+            assertEquals(Key.get(String.class, Names.named("d")), command.getKey());
+            command.acceptScopingVisitor(new FailingBindScopingVisitor() {
+              @Override public Void visitNoScoping() {
+                return null;
+              }
+            });
+            return null;
+          }
+        }
+    );
+  }
+
+  public void checkInjector(Module module, Element.Visitor<?>... visitors) {
+    Injector injector = Guice.createInjector(module);
+
+    List<Binding<?>> bindings = Lists.newArrayList(
+        Iterables.filter(injector.getBindings().values(), isUserBinding));
+    orderByKey.sort(bindings);
+
+    assertEquals(bindings.size(), visitors.length);
+
+    for (int i = 0; i < visitors.length; i++) {
+      Element.Visitor<?> visitor = visitors[i];
+      Binding<?> binding = bindings.get(i);
+      binding.acceptVisitor(visitor);
+    }
+  }
+
+  private final Predicate<Binding<?>> isUserBinding = new Predicate<Binding<?>>() {
+    private final ImmutableSet<Key<?>> BUILT_IN_BINDINGS = ImmutableSet.of(
+        Key.get(Injector.class), Key.get(Stage.class), Key.get(Logger.class));
+    public boolean apply(@Nullable Binding<?> binding) {
+      return !BUILT_IN_BINDINGS.contains(binding.getKey());
+    }
+  };
+
+  private final Ordering<Binding<?>> orderByKey = new Ordering<Binding<?>>() {
+    public int compare(Binding<?> a, Binding<?> b) {
+      return a.getKey().toString().compareTo(b.getKey().toString());
+    }
+  };
+
+  private static class StringProvider implements Provider<String> {
+    public String get() {
+      return "A";
+    }
+  }
+
+  private static class C { }
+
+  private static class D extends C {
+    @Inject public D(Injector unused) { }
+  }
+}