binding to the same interface twice

1,983 views
Skip to first unread message

Vasudeva Nori

unread,
May 13, 2008, 5:59:21 PM5/13/08
to google...@googlegroups.com
Writing a system integration test where I want to test the server logic
spanning several classes but NOT all classes of the server.
Say, the last class in the chain of server classes being tested is ClassB.
Say, ClassB has injection of another object. To control the execution of the
server logic beyond ClassB, I will need to change the binding of the
injection in ClassB. So, I need to "rebind" that class binding, to some
other mocked class.

Server, upon startup, reads all guice bindings from a Guice Module class.
I could make the server startup using a different Guice module class - in
which case, I will copy all bindings from the oriiginal guice module class
of the server and just change the binding for the one I want.
That works.

But now, there is duplication of code for Guice Module. If someone adds a
new binding for some other class in the Server's Guice Module class, that
binding should be added to the same file on the tests dir tree. Not nice.

ideally, I would like to have the server startup with guice bindings read
from 2 modules: one is the original guice module on the server. The other
guice module, used just by the tests, has just one binding declared in it -
for the one I want to override in ClassB.
But, binding twice to the same interface is not acceptable to guice.

is there any "trick" to have guice take the latest binding and ignore the
previous binding?

thanks

Sam Berlin

unread,
May 13, 2008, 6:13:40 PM5/13/08
to google...@googlegroups.com
I wrote a patch just the other day to do this. It requires the latest
SVN head of Guice. I sent an iteration of the patch to this list, but
rewrote it pretty heavily and sent the newest version to Jesse
directly. I'll send the most up-to-date version to this list again,
if anyone wants to make use of it. (Will have to send it tonight when
I get home, though.)

Sam

Sam Berlin

unread,
May 13, 2008, 7:29:46 PM5/13/08
to google...@googlegroups.com
The attached patch will let you do:

Module newModule = Modules.override(new ProductionModule())
.with(new TestModule())
.build();

TestModule can contains bindings that will override the equivalent
binding from ProductionModule. (Any not-overriden binding remains.)

You'll need to apply the patch against Guice SVN head in order for it
to work. (It could work entirely as an extension, except that
CommandReplacer creates instances of AddMessageErrorCommand, which has
a package-private constructor.)

If you're just looking for something out-of-the-box, SVN head also has
a 'InterceptingInjectorBuilder' (in extensions/commands), which lets
you replace bindings with instances of another class. This works very
well for mocks or stubs. (It doesn't work if you need the replacement
to have other objects injected into it.)

There's also a class in GuiceBerry that Jesse pointed out to me:
http://code.google.com/p/guiceberry/source/browse/trunk/src/com/google/inject/testing/guiceberry/InjectionController.java
. That uses InterceptingInjectorBuilder, so it has the same
limitations (can only replace bindings with live instances), but it
lets you do the replacements after the Injector has been created.

As far as I can tell, there's no way to put this functionality in
Guice 1.0 -- the commands infrastructure (which is only recently in
SVN) is what enables stuff like this to work.

Sam

changes.txt

Josh McDonald

unread,
May 13, 2008, 7:35:51 PM5/13/08
to google...@googlegroups.com
Seems to be a pretty regularly requested feature, I hope this goes into the next release. The more Guice gets acceptance out there, the better my chances of selling it to the powers-that-be here :)

-J

Index: src/com/google/inject/commands/CommandReplacer.java
===================================================================
--- src/com/google/inject/commands/CommandReplacer.java (revision 0)
+++ src/com/google/inject/commands/CommandReplacer.java (revision 0)
@@ -0,0 +1,221 @@
+package com.google.inject.commands;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import com.google.inject.Key;
+
+/**
+ * Replaces commands in an original list with matching ones
+ * from a set of replacements.
+ *
+ * @author sbe...@gmail.com (Sam Berlin)
+ */
+public class CommandReplacer {
+    private final boolean tolerateUnmatched;
+
+    public CommandReplacer(boolean tolerateUnmatched) {
+        this.tolerateUnmatched = tolerateUnmatched;
+    }
+
+    /**
+     * Returns a list of commands based on originals,
+     * but with all matching commands from replacements swapped
+     * out with the replacements.
+     * If there were any errors while performing the replacements,
+     * the errors are added as commands to the returned list.
+     */
+    public List<Command> replaceCommands(List<Command> originals, List<Command> replacements) {
+        List<Command> replacedCommands = new ArrayList<Command>();
+
+        // Look through our original commands to find any replacements.
+        ReplaceVisitor replaceVisitor = new ReplaceVisitor(replacements);
+        for(Command command : originals) {
+            Command replacement = command.acceptVisitor(replaceVisitor);
+            if(replacement != null)
+                replacedCommands.add(replacement);
+            else
+                replacedCommands.add(command);
+        }
+
+        // If we can tolerate all unmatched replacements, add them all without being picky.
+        if(tolerateUnmatched) {
+            replacedCommands.addAll(replaceVisitor.getRemainingReplacements());
+        } else {
+            // Otherwise, if we can't tolerate unmatched replacements, only add back
+            // ones that had matching keys replaced or don't require matches.
+            // (Adding back ones with matching keys allows the injector to
+            //  correctly fail later with an error that there's multiple bindings
+            //  for a single key.)
+            // If there's any commands that had no matching replacement, add
+            // an error that describes the unmatched binding.
+            MovingVisitor movingVisitor = new MovingVisitor(replaceVisitor.getReplacedKeys());
+            for(Command command : replaceVisitor.getRemainingReplacements()) {
+                if(command.acceptVisitor(movingVisitor)) {
+                    replacedCommands.add(command);
+                } else {
+                    replacedCommands.add(new AddMessageErrorCommand(command.getSource(),
+                            "Binding exists as replacement but is not replacing anything.  "
+                              +  "You might want to use ModuleBuilder.tolerateUnmatched.",
+                          new Object[0]));
+                }
+            }
+        }
+
+        return Collections.unmodifiableList(replacedCommands);
+    }
+
+
+    /**
+     * Returns a replacement command, based on available replacements.
+     * This keeps state through multiple visitations, to prevent duplicate commands
+     * from being replaced more than once (allowing duplicate commands to correctly
+     * blow up as an error).
+     */
+    private static class ReplaceVisitor extends DefaultVisitor<Command> {
+        private final Set<Key<?>> replacedKeys = new HashSet<Key<?>>();
+        private final List<Command> replacements = new ArrayList<Command>();
+
+        public ReplaceVisitor(List<Command> replacements) {
+            super(null); // default value is null: no replacement
+            this.replacements.addAll(replacements);
+        }
+
+        public List<Command> getRemainingReplacements() {
+            return Collections.unmodifiableList(replacements);
+        }
+
+        public Set<Key<?>> getReplacedKeys() {
+            return Collections.unmodifiableSet(replacedKeys);
+        }
+
+        @Override public <T> Command visitBind(BindCommand<T> command) {
+            return replaceFromKey(command.getKey());
+        }
+
+        @Override public Command visitBindConstant(BindConstantCommand command) {
+            return replaceFromKey(command.getKey());
+        }
+
+        private Command replaceFromKey(Key<?> key) {
+            if(!replacedKeys.contains(key)) {
+                KeyMatchVisitor keyMatcher = new KeyMatchVisitor(key);
+                for(Iterator<Command> commands = replacements.iterator(); commands.hasNext(); ) {
+                    Command command = commands.next();
+                    if(command.acceptVisitor(keyMatcher)) {
+                        replacedKeys.add(key);
+                        commands.remove();
+                        return command;
+                    }
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Returns true if the command matches the given key.
+     */
+    private static class KeyMatchVisitor extends DefaultVisitor<Boolean> {
+        private final Key<?> key;
+        public KeyMatchVisitor(Key<?> key) {
+            super(false);
+            this.key = key;
+        }
+
+        @Override public <T> Boolean visitBind(BindCommand<T> command) {
+            return command.getKey().equals(key);
+        }
+
+        @Override public Boolean visitBindConstant(BindConstantCommand command) {
+            return command.getKey().equals(key);
+        }
+    }
+
+    /**
+     * Returns true if this command should be moved.
+     * Key-based commands are only moved if the keys are within allowableKeys.
+     * The only other commands that move are:
+     *  {@link AddThrowableErrorCommand}, {@link AddMessageErrorCommand},
+     *  and {@link GetProviderCommand}.
+     */
+    private static class MovingVisitor extends DefaultVisitor<Boolean> {
+        private final Set<Key<?>> allowableKeys;
+
+        MovingVisitor(Set<Key<?>> allowableKeys) {
+            super(false);
+            this.allowableKeys = allowableKeys;
+        }
+
+        @Override public Boolean visitAddError(AddThrowableErrorCommand command) {
+            return true;
+        }
+
+        @Override public Boolean visitAddMessageError(AddMessageErrorCommand command) {
+            return true;
+        }
+
+        @Override public <T> Boolean visitBind(BindCommand<T> command) {
+            return allowableKeys.contains(command.getKey());
+        }
+
+        @Override public Boolean visitBindConstant(BindConstantCommand command) {
+            return allowableKeys.contains(command.getKey());
+        }
+
+        @Override public <T> Boolean visitGetProvider(GetProviderCommand<T> command) {
+            return true;
+        }
+    }
+
+    /** A Visitor that returns a default value. */
+    private static class DefaultVisitor<V> implements Command.Visitor<V> {
+        private final V defaultValue;
+
+        DefaultVisitor(V defaultValue) {
+            this.defaultValue = defaultValue;
+        }
+
+        public V visitAddError(AddThrowableErrorCommand command) {
+            return defaultValue;
+        }
+
+        public V visitAddMessageError(AddMessageErrorCommand command) {
+            return defaultValue;
+        }
+
+        public <T> V visitBind(BindCommand<T> command) {
+            return defaultValue;
+        }
+
+        public V visitBindConstant(BindConstantCommand command) {
+            return defaultValue;
+        }
+
+        public V visitBindInterceptor(BindInterceptorCommand command) {
+            return defaultValue;
+        }
+
+        public V visitBindScope(BindScopeCommand command) {
+            return defaultValue;
+        }
+
+        public V visitConvertToTypes(ConvertToTypesCommand command) {
+            return defaultValue;
+        }
+
+        public <T> V visitGetProvider(GetProviderCommand<T> command) {
+            return defaultValue;
+        }
+
+        public V visitRequestStaticInjection(
+                RequestStaticInjectionCommand command) {
+            return defaultValue;
+        }
+
+    }
+}
\ No newline at end of file
Index: extensions/commands/test/com/google/inject/commands/intercepting/ModulesTest.java
===================================================================
--- extensions/commands/test/com/google/inject/commands/intercepting/ModulesTest.java   (revision 0)
+++ extensions/commands/test/com/google/inject/commands/intercepting/ModulesTest.java   (revision 0)
@@ -0,0 +1,279 @@
+package com.google.inject.commands.intercepting;
+
+import java.util.ArrayList;
+
+import junit.framework.TestCase;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.CreationException;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.name.Names;
+
+public class ModulesTest extends TestCase {
+
+    public void testOverride() {
+        Module replacements = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("B");
+            }
+        };
+
+        Module original = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("A");
+            }
+        };
+
+        Module m = Modules.override(original).with(replacements).build();
+        Injector injector = Guice.createInjector(m);
+
+        assertEquals("B", injector.getInstance(String.class));
+    }
+
+    public void testOverrideUnmatchedUntolerated() {
+        Module replacements = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("B");
+                bind(Integer.class).toInstance(5);
+            }
+        };
+
+        Module m = Modules.override().with(replacements).build();
+
+        try {
+            Guice.createInjector(m);
+            fail();
+        } catch (CreationException ce) {
+            // TODO: test it points to the different bind lines.
+            assertTrue(ce.getMessage().contains("1) Error at " + replacements.getClass().getName()));
+            assertTrue(ce.getMessage().contains("2) Error at " + replacements.getClass().getName()));
+            assertFalse(ce.getMessage().contains("3) Error at"));
+        }
+    }
+
+    public void testOverrideUnmatchedTolerated() {
+        Module replacements = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("B");
+            }
+        };
+
+        Module m = Modules.override().with(replacements).tolerateUnmatched().build();
+        Injector injector = Guice.createInjector(m);
+
+        assertEquals("B", injector.getInstance(String.class));
+    }
+
+    public void testOverrideConstant() {
+        Module replacements = new AbstractModule() {
+            protected void configure() {
+                bindConstant().annotatedWith(Names.named("Test")).to("B");
+            }
+        };
+
+        Module original = new AbstractModule() {
+            protected void configure() {
+                bindConstant().annotatedWith(Names.named("Test")).to("A");
+            }
+        };
+
+        Module m = Modules.override(original).with(replacements).build();
+        Injector injector = Guice.createInjector(m);
+
+        assertEquals("B", injector.getInstance(Key.get(String.class, Names.named("Test"))));
+    }
+
+    public void testGetProviderInModule() {
+        Module original = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("A");
+                bind(String.class).annotatedWith(Names.named("Test")).toProvider(getProvider(String.class));
+            }
+        };
+
+        Module m = Modules.override(original).with().build();
+        Injector injector = Guice.createInjector(m);
+
+        assertEquals("A", injector.getInstance(String.class));
+        assertEquals("A", injector.getInstance(Key.get(String.class, Names.named("Test"))));
+    }
+
+    public void testOverrideWhatGetProviderProvided() {
+        Module replacements = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("B");
+            }
+        };
+
+        Module original = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("A");
+                bind(String.class).annotatedWith(Names.named("Test")).toProvider(getProvider(String.class));
+            }
+        };
+
+        Module m = Modules.override(original).with(replacements).build();
+        Injector injector = Guice.createInjector(m);
+
+        assertEquals("B", injector.getInstance(String.class));
+        assertEquals("B", injector.getInstance(Key.get(String.class, Names.named("Test"))));
+    }
+
+    public void testOverrideUsingOriginalsGetProvider() {
+        Module replacements = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toProvider(getProvider(Key.get(String.class, Names.named("Test"))));
+            }
+        };
+
+        Module original = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("A");
+                bind(String.class).annotatedWith(Names.named("Test")).toInstance("B");
+            }
+        };
+
+        Module m = Modules.override(original).with(replacements).build();
+        Injector injector = Guice.createInjector(m);
+
+        assertEquals("B", injector.getInstance(String.class));
+        assertEquals("B", injector.getInstance(Key.get(String.class, Names.named("Test"))));
+    }
+
+    public void testManyOverrides() {
+        Module r1 = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("B");
+                bind(String.class).annotatedWith(Names.named("r1")).toInstance("B1");
+            }
+        };
+
+        Module r2 = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("C");
+                bind(String.class).annotatedWith(Names.named("r2")).toInstance("C2");
+            }
+        };
+
+        Module o = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("A");
+                bind(String.class).annotatedWith(Names.named("r1")).toInstance("A1");
+                bind(String.class).annotatedWith(Names.named("r2")).toInstance("A2");
+            }
+        };
+
+        Module m = Modules.override(Modules.override(o).with(r1).build()).with(r2).build();
+        Injector injector = Guice.createInjector(m);
+
+        assertEquals("C", injector.getInstance(String.class));
+        assertEquals("B1", injector.getInstance(Key.get(String.class, Names.named("r1"))));
+        assertEquals("C2", injector.getInstance(Key.get(String.class, Names.named("r2"))));
+    }
+
+    public void testOverridesTwiceFails() {
+        Module r1 = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("B");
+            }
+        };
+
+        Module r2 = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("C");
+            }
+        };
+
+        Module o = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("A");
+            }
+        };
+
+        Module m = Modules.override(o).with(r1, r2).build();
+        try {
+            Guice.createInjector(m);
+            fail();
+        } catch(CreationException ce) {
+            assertTrue(ce.getMessage().contains("Error at " + r2.getClass().getName()));
+            assertTrue(ce.getMessage().contains("A binding to java.lang.String was already configured at " + r1.getClass().getName()));
+        }
+    }
+
+    public void testOverridesTwiceFailsEvenIfTolerated() {
+        Module r1 = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("B");
+            }
+        };
+
+        Module r2 = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("C");
+            }
+        };
+
+        Module o = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("A");
+            }
+        };
+
+        Module m = Modules.override(o).with(r1, r2).tolerateUnmatched().build();
+        try {
+            Guice.createInjector(m);
+            fail();
+        } catch(CreationException ce) {
+            assertTrue(ce.getMessage().contains("Error at " + r2.getClass().getName()));
+            assertTrue(ce.getMessage().contains("A binding to java.lang.String was already configured at " + r1.getClass().getName()));
+        }
+    }
+
+    public void testOverridesDoesntFixTwiceBoundInOriginal() {
+        Module r1 = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("C");
+            }
+        };
+
+        Module o = new AbstractModule() {
+            protected void configure() {
+                bind(String.class).toInstance("A");
+                bind(String.class).toInstance("B");
+            }
+        };
+
+        Module m = Modules.override(o).with(r1).build();
+        try {
+            Guice.createInjector(m);
+            fail();
+        } catch(CreationException ce) {
+            assertTrue(ce.getMessage().contains("Error at " + o.getClass().getName()));
+            assertTrue(ce.getMessage().contains("A binding to java.lang.String was already configured at " + r1.getClass().getName()));
+        }
+    }
+
+    public void testOverrideBare() {
+        Module replacements = new AbstractModule() {
+            @SuppressWarnings({ "unchecked" })
+            protected void configure() {
+                bind(ArrayList.class).toInstance(new ArrayList() {});
+            }
+        };
+
+        Module original = new AbstractModule() {
+            protected void configure() {
+                bind(ArrayList.class);
+            }
+        };
+
+        Module m = Modules.override(original).with(replacements).build();
+        Injector injector = Guice.createInjector(m);
+        // Make sure it's bound to the anonymous inner class.
+        assertTrue(injector.getInstance(ArrayList.class).getClass().getName().contains("$"));
+    }
+
+}

Property changes on: extensions/commands/test/com/google/inject/commands/intercepting/ModulesTest.java
___________________________________________________________________
Name: svn:executable
  + *

Index: extensions/commands/test/com/google/inject/commands/intercepting/AllTests.java
===================================================================
--- extensions/commands/test/com/google/inject/commands/intercepting/AllTests.java      (revision 467)
+++ extensions/commands/test/com/google/inject/commands/intercepting/AllTests.java      (working copy)
@@ -29,6 +29,7 @@
    TestSuite suite = new TestSuite();

    suite.addTestSuite(InterceptingInjectorBuilderTest.class);
+    suite.addTestSuite(ModulesTest.class);

    return suite;
  }
Index: extensions/commands/src/com/google/inject/commands/intercepting/Modules.java
===================================================================
--- extensions/commands/src/com/google/inject/commands/intercepting/Modules.java        (revision 0)
+++ extensions/commands/src/com/google/inject/commands/intercepting/Modules.java        (revision 0)
@@ -0,0 +1,151 @@
+package com.google.inject.commands.intercepting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import com.google.inject.Binder;
+import com.google.inject.ImplementedBy;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.ProvidedBy;
+import com.google.inject.commands.Command;
+import com.google.inject.commands.CommandRecorder;
+import com.google.inject.commands.CommandReplacer;
+import com.google.inject.commands.CommandReplayer;
+import com.google.inject.commands.EarlyRequestsProvider;
+import com.google.inject.commands.FutureInjector;
+import com.google.inject.internal.UniqueAnnotations;
+
+/**
+ * Builds {@link Module Modules} with replacement bindings.
+ *
+ * <h3>Usage Examples</h3>
+ *
+ * <pre>
+ *  Module replaced = Modules.override(new ProductionModule())
+ *                           .with(new TestModule())
+ *                           .build();
+ * </pre>
+ *
+ * Using the above code, all bindings within TestModule will replace those
+ * from ProductionModule.  Any bindings from ProductionModule not replaced
+ * from TestModule will remain.  If TestModule has additional bindings
+ * that are not replacing anything from ProductionModule, then a CreationException
+ * is thrown, listing which bindings are extraneous.<p>
+ * If you prefer to allow extraneous bindings, you can call
+ * {@link ModuleReplacementBuilder#tolerateUnmatched()}, such as:
+ * <pre>
+ *  Module replaced = Modules.override(new ProductionModule())
+ *                           .with(new TestModule())
+ *                           .tolerateUnmatched()
+ *                           .build();
+ * </pre>
+ * It is possible to create many layers of replacement modules by doing something like:
+ * <pre>
+ *   Module firstReplacement = Modules.override(new ProductionModule())
+ *                                    .with(new StagingEnvironmentModule())
+ *                                    .build();
+ *   Module secondReplacement = Modules.override(firstReplacement)
+ *                                     .with(new TestModule())
+ *                                     .build();
+ * </pre>
+ * This is useful if StagingEnvironmentModule replacements things from ProductionModule,
+ * but TestModule replaces some things from both StagingEnvironmentModule and
+ * ProductionModule.  (If both StagingEnvironmentModule and TestModule were provided
+ * as replacements at the same time, Guice would blow up as-is usual when two
+ * bindings are bound to the same key.)
+ *
+ * <h3>Limitations of the current implementation</h3>
+ *
+ * <p>If {@link ModuleReplacementBuilder#tolerateUnmatched()} is not set, all intercepted
+ * bindings must be bound explicitly. In this case, overriding cannot
+ * be applied to implicit bindings, or bindings that depend on
+ * {@literal @}{@link ProvidedBy}, {@literal @}{@link ImplementedBy}
+ * annotations.  Additionally, if tolerateUnmatched is not set, the replacement module
+ * cannot contain any binding requests other than normal bindings or constant bindings.
+ *
+ * @author sbe...@gmail.com (Sam Berlin)
+ */
+public class Modules {
+
+    private Modules() {}
+
+    /**
+     * See {@link Modules} for examples.
+     */
+    public static ModuleReplacedWithBuilder override(Module... modules) {
+        return new Builder().override(modules);
+    }
+
+    /**
+     * See {@link Modules} for examples.
+     */
+    public static ModuleReplacedWithBuilder override(List<? extends Module> modules) {
+        return new Builder().override(modules);
+    }
+
+    private static class Builder implements ModuleReplacedWithBuilder, ModuleReplacementBuilder {
+        private final Collection<Module> replacementsModules = new ArrayList<Module>();
+        private final Collection<Module> originalModules = new ArrayList<Module>();
+        private boolean tolerateUnmatched = false;
+
+        public ModuleReplacedWithBuilder override(List<? extends Module> modules) {
+            this.originalModules.addAll(modules);
+            return this;
+        }
+
+        public ModuleReplacedWithBuilder override(Module... modules) {
+            this.originalModules.addAll(Arrays.asList(modules));
+            return this;
+        }
+
+        public ModuleReplacementBuilder with(Module... modules) {
+            this.replacementsModules.addAll(Arrays.asList(modules));
+            return this;
+        }
+
+        public ModuleReplacementBuilder with(List<? extends Module> modules) {
+           this.replacementsModules.addAll(modules);
+           return this;
+        }
+
+        public Module build() {
+            InjectableFutureInjector futureInjector = new InjectableFutureInjector();
+            originalModules.add(futureInjector);
+            List<Command> commands = new CommandRecorder(futureInjector).recordCommands(originalModules);
+            List<Command> replacements = new CommandRecorder(futureInjector).recordCommands(replacementsModules);
+            List<Command> replacedCommands = new CommandReplacer(tolerateUnmatched).replaceCommands(commands, replacements);
+            Module module = new CommandReplayer().createModule(replacedCommands);
+            return module;
+        }
+
+        public ModuleReplacementBuilder tolerateUnmatched() {
+            tolerateUnmatched = true;
+            return this;
+        }
+
+    }
+
+    /** A {@link FutureInjector} that can initialize itself by being added as a module. */
+    private static class InjectableFutureInjector implements EarlyRequestsProvider, Module {
+        private @Inject Injector injector;
+
+        public <T> T get(Key<T> key) {
+          if (injector == null) {
+            throw new IllegalStateException("This provider cannot be used until the"
+                + " Injector has been created.");
+          }
+          return injector.getInstance(key);
+        }
+
+        public void configure(Binder binder) {
+            binder.bind(Object.class)
+                .annotatedWith(UniqueAnnotations.create())
+                .toInstance(this);
+        }
+    }
+}

Property changes on: extensions/commands/src/com/google/inject/commands/intercepting/Modules.java
___________________________________________________________________
Name: svn:executable
  + *

Index: extensions/commands/src/com/google/inject/commands/intercepting/ModuleReplacedWithBuilder.java
===================================================================
--- extensions/commands/src/com/google/inject/commands/intercepting/ModuleReplacedWithBuilder.java      (revision 0)
+++ extensions/commands/src/com/google/inject/commands/intercepting/ModuleReplacedWithBuilder.java      (revision 0)
@@ -0,0 +1,27 @@
+package com.google.inject.commands.intercepting;
+
+import java.util.List;
+
+import com.google.inject.Module;
+
+/**
+ * See {@link Modules} for usage examples.
+ *
+ * @author sbe...@gmail.com (Sam Berlin)
+ */
+public interface ModuleReplacedWithBuilder {
+
+    /**
+     * Sets the modules to use as replacements.
+     *
+     * See the examples at {@link Modules}.
+     */
+    ModuleReplacementBuilder with(Module... modules);
+
+    /**
+     * Sets the modules to use as replacements.
+     *
+     * See the examples at {@link Modules}.
+     */
+    ModuleReplacementBuilder with(List<? extends Module> modules);
+}
Index: extensions/commands/src/com/google/inject/commands/intercepting/ModuleReplacementBuilder.java
===================================================================
--- extensions/commands/src/com/google/inject/commands/intercepting/ModuleReplacementBuilder.java       (revision 0)
+++ extensions/commands/src/com/google/inject/commands/intercepting/ModuleReplacementBuilder.java       (revision 0)
@@ -0,0 +1,31 @@
+package com.google.inject.commands.intercepting;
+
+import com.google.inject.Module;
+
+/**
+ * A factory for building modules.
+ *
+ * See {@link Modules} for examples.
+ *
+ * @author sbe...@gmail.com (Sam Berlin)
+ */
+public interface ModuleReplacementBuilder {
+
+    /**
+     * Constructs the module.
+     *
+     * See {@link Modules} for examples.
+     */
+    Module build();
+
+    /**
+     * Instructs the builder to allow unmatched replacements.
+     * This should be used if you want bindings in replacement
+     * modules to be allowed even if they are not replacing
+     * anything from the original modules.
+     *
+     * See {@link Modules} for examples.
+     */
+    ModuleReplacementBuilder tolerateUnmatched();
+
+}




--
"Therefore, send not to know For whom the bell tolls. It tolls for thee."

:: Josh 'G-Funk' McDonald
:: 0437 221 380 :: jo...@gfunk007.com
Reply all
Reply to author
Forward
0 new messages