Initial checkin of Sam Berlin's contribution for Module overrides. We still might want a small DSL to replace the single two-argument method.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@486 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/Guice.java b/src/com/google/inject/Guice.java
index 7c23e72..b834164 100644
--- a/src/com/google/inject/Guice.java
+++ b/src/com/google/inject/Guice.java
@@ -16,7 +16,13 @@
 
 package com.google.inject;
 
+import com.google.inject.commands.*;
+import com.google.inject.internal.UniqueAnnotations;
+
 import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * The entry point to the Guice framework. Creates {@link Injector}s from
@@ -135,4 +141,63 @@
         .build();
   }
 
+  /**
+   * Returns a new {@link Module} that overlays {@code overridesModule} over
+   * {@code module}. If a key is bound by both modules, only the binding in
+   * overrides is kept. This can be used to replace bindings in a production
+   * module with test bindings:
+   * <pre>
+   * Module functionalTestModule
+   *     = Guice.overrideModule(new ProductionModule(), new TestModule());
+   * </pre>
+   */
+  public static Module overrideModule(Module module, Module overridesModule) {
+    final FutureInjector futureInjector = new FutureInjector();
+    CommandRecorder commandRecorder = new CommandRecorder(futureInjector);
+    final List<Command> commands = commandRecorder.recordCommands(module);
+    final List<Command> overrideCommands = commandRecorder.recordCommands(overridesModule);
+
+    return new AbstractModule() {
+      public void configure() {
+        final Set<Key> overriddenKeys = new HashSet<Key>();
+
+        bind(Object.class).annotatedWith(UniqueAnnotations.create())
+            .toInstance(new Object() {
+              @Inject void initialize(Injector injector) {
+                futureInjector.initialize(injector);
+              }
+            });
+
+        // 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);
+          }
+          @Override public void replayBindConstant(Binder binder, BindConstantCommand command) {
+            overriddenKeys.add(command.getKey());
+            super.replayBindConstant(binder, command);
+          }
+        }.replay(binder(), overrideCommands);
+
+        // 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);
+            }
+          }
+          @Override public void replayBindConstant(Binder binder, BindConstantCommand command) {
+            if (!overriddenKeys.remove(command.getKey())) {
+              super.replayBindConstant(binder, command);
+            }
+          }
+        }.replay(binder(), commands);
+
+        // TODO: bind the overridden keys using multibinder
+      }
+    };
+  }
 }
diff --git a/src/com/google/inject/commands/CommandReplayer.java b/src/com/google/inject/commands/CommandReplayer.java
index d4326fb..f1a1b28 100644
--- a/src/com/google/inject/commands/CommandReplayer.java
+++ b/src/com/google/inject/commands/CommandReplayer.java
@@ -19,12 +19,12 @@
 import com.google.inject.Binder;
 import com.google.inject.Key;
 import com.google.inject.Module;
-import com.google.inject.spi.SourceProviders;
 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 com.google.inject.internal.Objects;
+import com.google.inject.spi.SourceProviders;
 import org.aopalliance.intercept.MethodInterceptor;
 
 import java.util.List;
@@ -40,7 +40,7 @@
    * Returns a module that executes the specified commands
    * using this executing visitor.
    */
-  public Module createModule(final Iterable<Command> commands) {
+  public final Module createModule(final Iterable<Command> commands) {
     return new Module() {
       public void configure(Binder binder) {
         replay(binder, commands);
@@ -51,7 +51,7 @@
   /**
    * Replays {@code commands} against {@code binder}.
    */
-  public void replay(final Binder binder, Iterable<Command> commands) {
+  public final void replay(final Binder binder, Iterable<Command> commands) {
     Objects.nonNull(binder, "binder");
     Objects.nonNull(commands, "commands");
 
diff --git a/src/com/google/inject/commands/DefaultCommandVisitor.java b/src/com/google/inject/commands/DefaultCommandVisitor.java
new file mode 100644
index 0000000..08f433b
--- /dev/null
+++ b/src/com/google/inject/commands/DefaultCommandVisitor.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.google.inject.commands;
+
+/**
+ * No-op visitor for subclassing. All interface methods simply delegate to
+ * {@link #visitCommand(Command)}, returning its result.
+ *
+ * @author sberlin@gmail.com (Sam Berlin)
+ */
+public class DefaultCommandVisitor<V> implements Command.Visitor<V> {
+
+  protected DefaultCommandVisitor() {}
+
+  /**
+   * Visit {@code command} and return a result.
+   */
+  public V visitCommand(Command command) {
+    return null;
+  }
+
+  public V visitAddError(AddThrowableErrorCommand command) {
+    return visitCommand(command);
+  }
+
+  public V visitAddMessageError(AddMessageErrorCommand command) {
+    return visitCommand(command);
+  }
+
+  public <T> V visitBind(BindCommand<T> command) {
+    return visitCommand(command);
+  }
+
+  public V visitBindConstant(BindConstantCommand command) {
+    return visitCommand(command);
+  }
+
+  public V visitBindInterceptor(BindInterceptorCommand command) {
+    return visitCommand(command);
+  }
+
+  public V visitBindScope(BindScopeCommand command) {
+    return visitCommand(command);
+  }
+
+  public V visitConvertToTypes(ConvertToTypesCommand command) {
+    return visitCommand(command);
+  }
+
+  public <T> V visitGetProvider(GetProviderCommand<T> command) {
+    return visitCommand(command);
+  }
+
+  public V visitRequestStaticInjection(
+      RequestStaticInjectionCommand command) {
+    return visitCommand(command);
+  }
+}