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);
+ }
+}