visitable to commands
git-svn-id: https://google-guice.googlecode.com/svn/trunk@405 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/extensions/commands/build.properties b/extensions/commands/build.properties
new file mode 100644
index 0000000..e85ddc4
--- /dev/null
+++ b/extensions/commands/build.properties
@@ -0,0 +1,5 @@
+lib.dir=../../lib
+src.dir=src
+test.dir=test
+build.dir=build
+test.class=com.google.inject.commands.CommandRecorderTest
diff --git a/extensions/commands/build.xml b/extensions/commands/build.xml
new file mode 100644
index 0000000..12f94a8
--- /dev/null
+++ b/extensions/commands/build.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<project name="guice-visitable" basedir="." default="jar">
+
+ <import file="../../common.xml"/>
+
+ <path id="compile.classpath">
+ <fileset dir="${lib.dir}" includes="*.jar"/>
+ <fileset dir="${lib.dir}/build" includes="*.jar"/>
+ <fileset dir="../../build/dist" includes="*.jar"/>
+ </path>
+
+ <target name="jar" depends="compile"
+ description="Build jar.">
+ <mkdir dir="${build.dir}"/>
+ <jar destfile="${build.dir}/${ant.project.name}-${version}.jar">
+ <fileset dir="${build.dir}/classes"/>
+ </jar>
+ </target>
+
+</project>
diff --git a/extensions/commands/commands.iml b/extensions/commands/commands.iml
new file mode 100644
index 0000000..bc42261
--- /dev/null
+++ b/extensions/commands/commands.iml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="guice" />
+ <orderEntryProperties />
+ </component>
+</module>
+
diff --git a/extensions/commands/src/com/google/inject/commands/AddMessageErrorCommand.java b/extensions/commands/src/com/google/inject/commands/AddMessageErrorCommand.java
new file mode 100644
index 0000000..d6f0077
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/AddMessageErrorCommand.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.commands;
+
+import java.util.Arrays;
+import static java.util.Collections.unmodifiableList;
+import java.util.List;
+
+/**
+ * Immutable snapshot of a request to add a string message.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class AddMessageErrorCommand implements Command {
+ private final String message;
+ private final List<Object> arguments;
+
+ AddMessageErrorCommand(String message, Object[] arguments) {
+ this.message = message;
+ this.arguments = unmodifiableList(Arrays.asList(arguments.clone()));
+ }
+
+ public <T> T acceptVisitor(Visitor<T> visitor) {
+ return visitor.visitAddMessageError(this);
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public List<Object> getArguments() {
+ return arguments;
+ }
+}
diff --git a/extensions/commands/src/com/google/inject/commands/AddThrowableErrorCommand.java b/extensions/commands/src/com/google/inject/commands/AddThrowableErrorCommand.java
new file mode 100644
index 0000000..38be96e
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/AddThrowableErrorCommand.java
@@ -0,0 +1,38 @@
+/**
+ * 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;
+
+/**
+ * Immutable snapshot of a request to add a throwable message.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class AddThrowableErrorCommand implements Command {
+ private final Throwable throwable;
+
+ AddThrowableErrorCommand(Throwable throwable) {
+ this.throwable = throwable;
+ }
+
+ public <T> T acceptVisitor(Visitor<T> visitor) {
+ return visitor.visitAddError(this);
+ }
+
+ public Throwable getThrowable() {
+ return throwable;
+ }
+}
diff --git a/extensions/commands/src/com/google/inject/commands/BindCommand.java b/extensions/commands/src/com/google/inject/commands/BindCommand.java
new file mode 100644
index 0000000..c8ef971
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/BindCommand.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.commands;
+
+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.ConstantBindingBuilder;
+import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.binder.ScopedBindingBuilder;
+import static com.google.inject.internal.Objects.nonNull;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Immutable snapshot of a request to bind a value.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class BindCommand<T> implements Command {
+ private Key<T> key;
+ private BindTarget<T> bindTarget;
+ private BindScoping bindScoping;
+
+ BindCommand(Key<T> key) {
+ this.key = nonNull(key, "key");
+ }
+
+ public <V> V acceptVisitor(Visitor<V> visitor) {
+ return visitor.visitBind(this);
+ }
+
+ public Key<T> getKey() {
+ return key;
+ }
+
+ public BindTarget<T> getTarget() {
+ return bindTarget;
+ }
+
+ public BindScoping getScoping() {
+ return bindScoping;
+ }
+
+ @Override public String toString() {
+ return "bind " + key
+ + (bindTarget == null ? "" : (" to " + bindTarget))
+ + (bindScoping == null ? "" : (" in " + bindScoping));
+ }
+
+ private static abstract class AbstractTarget<T> implements BindTarget<T> {
+ public void execute(ConstantBindingBuilder builder) {
+ throw new UnsupportedOperationException();
+ }
+ public T get(T defaultValue) {
+ return defaultValue;
+ }
+ public Key<? extends Provider<? extends T>> getProviderKey(
+ Key<Provider<? extends T>> defaultValue) {
+ return defaultValue;
+ }
+ public Provider<? extends T> getProvider(Provider<? extends T> defaultValue) {
+ return defaultValue;
+ }
+ public Key<? extends T> getKey(Key<? extends T> defaultValue) {
+ return defaultValue;
+ }
+ }
+
+ private static abstract class AbstractScoping implements BindScoping {
+ public boolean isEagerSingleton() {
+ return false;
+ }
+ public Scope getScope(Scope defaultValue) {
+ return defaultValue;
+ }
+ public Class<? extends Annotation> getScopeAnnotation(
+ Class<? extends Annotation> defaultValue) {
+ return defaultValue;
+ }
+ }
+
+ BindingBuilder bindingBuilder() {
+ return new BindingBuilder();
+ }
+
+ /**
+ * Package-private write access to the internal state of this command.
+ */
+ class BindingBuilder implements AnnotatedBindingBuilder<T> {
+ public LinkedBindingBuilder<T> annotatedWith(
+ Class<? extends Annotation> annotationType) {
+ assertNotAnnotated();
+ key = Key.get(key.getTypeLiteral(), annotationType);
+ return this;
+ }
+
+ public LinkedBindingBuilder<T> annotatedWith(Annotation annotation) {
+ nonNull(annotation, "annotation");
+ assertNotAnnotated();
+ 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) {
+ nonNull(targetKey, "targetKey");
+ assertNoTarget();
+ bindTarget = new AbstractTarget<T>() {
+ public ScopedBindingBuilder execute(LinkedBindingBuilder<T> linkedBindingBuilder) {
+ return linkedBindingBuilder.to(targetKey);
+ }
+ @Override public Key<? extends T> getKey(Key<? extends T> defaultValue) {
+ return targetKey;
+ }
+ @Override public String toString() {
+ return String.valueOf(targetKey);
+ }
+ };
+ return this;
+ }
+
+ public void toInstance(final T instance) {
+ nonNull(instance, "instance"); // might someday want to tolerate null here
+ assertNoTarget();
+ bindTarget = new AbstractTarget<T>() {
+ public ScopedBindingBuilder execute(LinkedBindingBuilder<T> linkedBindingBuilder) {
+ linkedBindingBuilder.toInstance(instance);
+ return null;
+ }
+ @Override public T get(T defaultValue) {
+ return instance;
+ }
+ @Override public String toString() {
+ return "instance " + instance;
+ }
+ };
+ }
+
+ public ScopedBindingBuilder toProvider(final Provider<? extends T> provider) {
+ nonNull(provider, "provider");
+ assertNoTarget();
+ bindTarget = new AbstractTarget<T>() {
+ public ScopedBindingBuilder execute(LinkedBindingBuilder<T> linkedBindingBuilder) {
+ return linkedBindingBuilder.toProvider(provider);
+ }
+ @Override public Provider<? extends T> getProvider(Provider<? extends T> defaultValue) {
+ return provider;
+ }
+ @Override public String toString() {
+ return "provider " + 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) {
+ nonNull(providerKey, "providerKey");
+ assertNoTarget();
+ bindTarget = new AbstractTarget<T>() {
+ public ScopedBindingBuilder execute(LinkedBindingBuilder<T> linkedBindingBuilder) {
+ return linkedBindingBuilder.toProvider(providerKey);
+ }
+ @Override public Key<? extends Provider<? extends T>> getProviderKey(
+ Key<Provider<? extends T>> defaultValue) {
+ return providerKey;
+ }
+ @Override public String toString() {
+ return "provider " + providerKey;
+ }
+ };
+ return this;
+ }
+
+ public void in(final Class<? extends Annotation> scopeAnnotation) {
+ nonNull(scopeAnnotation, "scopeAnnotation");
+ assertNoScope();
+
+ bindScoping = new AbstractScoping() {
+ public void execute(ScopedBindingBuilder scopedBindingBuilder) {
+ scopedBindingBuilder.in(scopeAnnotation);
+ }
+ @Override public Class<? extends Annotation> getScopeAnnotation(
+ Class<? extends Annotation> defaultValue) {
+ return scopeAnnotation;
+ }
+ @Override public String toString() {
+ return scopeAnnotation.getName();
+ }
+ };
+ }
+
+ public void in(final Scope scope) {
+ nonNull(scope, "scope");
+ assertNoScope();
+ bindScoping = new AbstractScoping() {
+ public void execute(ScopedBindingBuilder scopedBindingBuilder) {
+ scopedBindingBuilder.in(scope);
+ }
+ @Override public Scope getScope(Scope defaultValue) {
+ return scope;
+ }
+ @Override public String toString() {
+ return String.valueOf(scope);
+ }
+ };
+ }
+
+ public void asEagerSingleton() {
+ assertNoScope();
+ bindScoping = new AbstractScoping() {
+ public void execute(ScopedBindingBuilder scopedBindingBuilder) {
+ scopedBindingBuilder.asEagerSingleton();
+ }
+ @Override public boolean isEagerSingleton() {
+ return true;
+ }
+ @Override public String toString() {
+ return "eager singleton";
+ }
+ };
+ }
+
+ private void assertNoTarget() {
+ if (bindTarget != null) {
+ throw new IllegalStateException("Already targetted to " + bindTarget);
+ }
+ }
+
+ private void assertNotAnnotated() {
+ if (BindCommand.this.key.getAnnotationType() != null) {
+ throw new IllegalStateException("Already annotated with " + key.getAnnotationType());
+ }
+ }
+
+ private void assertNoScope() {
+ if (bindScoping != null) {
+ throw new IllegalStateException("Already scoped by " + bindScoping);
+ }
+ }
+ }
+}
diff --git a/extensions/commands/src/com/google/inject/commands/BindConstantCommand.java b/extensions/commands/src/com/google/inject/commands/BindConstantCommand.java
new file mode 100644
index 0000000..142bb19
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/BindConstantCommand.java
@@ -0,0 +1,334 @@
+/**
+ * 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.inject.Key;
+import com.google.inject.Provider;
+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 java.lang.annotation.Annotation;
+
+/**
+ * Immutable snapshot of a request to bind a constant.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class BindConstantCommand implements Command {
+ private BindingAnnotation bindingAnnotation;
+ private ConstantTarget<?> target;
+
+ BindConstantCommand() {
+ // hide public constructor
+ }
+
+ public <T> T acceptVisitor(Visitor<T> visitor) {
+ return visitor.visitBindConstant(this);
+ }
+
+ public BindTarget<?> getTarget() {
+ return target;
+ }
+
+ public <T> Key<T> getKey() {
+ return bindingAnnotation.getKey();
+ }
+
+ /**
+ * Target API for bindConstant().
+ */
+ private static abstract class ConstantTarget<T> implements BindTarget<T> {
+
+ /**
+ * Returns the type of constant, such as {@code int.class} or
+ * {@code Enum.class}.
+ */
+ abstract Class getType();
+
+ public boolean hasInstance() {
+ return true;
+ }
+ public ScopedBindingBuilder execute(LinkedBindingBuilder linkedBindingBuilder) {
+ throw new UnsupportedOperationException();
+ }
+ public Provider<? extends T> getProvider(Provider<? extends T> defaultValue) {
+ return defaultValue;
+ }
+ public Key<? extends Provider<? extends T>> getProviderKey(
+ Key<Provider<? extends T>> defaultValue) {
+ return defaultValue;
+ }
+ public Key<? extends T> getKey(Key<? extends T> defaultValue) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Internal annotation API.
+ */
+ private abstract class BindingAnnotation {
+ abstract ConstantBindingBuilder execute(AnnotatedConstantBindingBuilder builder);
+ abstract <T> Key<T> getKey();
+ }
+
+ BindingBuilder bindingBuilder() {
+ return new BindingBuilder();
+ }
+
+ /**
+ * Package-private write access to the internal state of this command.
+ */
+ class BindingBuilder
+ implements AnnotatedConstantBindingBuilder, ConstantBindingBuilder {
+
+ private void assertNoBindingAnnotation() {
+ if (bindingAnnotation != null) {
+ throw new IllegalStateException("Already annotated with " + bindingAnnotation);
+ }
+ }
+
+ private void assertNoTarget() {
+ if (target != null) {
+ throw new IllegalStateException("Already targetted to " + target);
+ }
+ }
+
+ public ConstantBindingBuilder annotatedWith(final Class<? extends Annotation> 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) {
+ 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) {
+ assertNoTarget();
+
+ BindConstantCommand.this.target = new ConstantTarget() {
+ public void execute(ConstantBindingBuilder builder) {
+ builder.to(value);
+ }
+ public Object get(Object defaultValue) {
+ return value;
+ }
+ public Class getType() {
+ return String.class;
+ }
+ @Override public String toString() {
+ return value;
+ }
+ };
+ }
+
+ public void to(final int value) {
+ assertNoTarget();
+
+ BindConstantCommand.this.target = new ConstantTarget() {
+ public void execute(ConstantBindingBuilder builder) {
+ builder.to(value);
+ }
+ public Object get(Object defaultValue) {
+ return value;
+ }
+ public Class getType() {
+ return Integer.class;
+ }
+ @Override public String toString() {
+ return String.valueOf(value);
+ }
+ };
+ }
+
+ public void to(final long value) {
+ assertNoTarget();
+
+ BindConstantCommand.this.target = new ConstantTarget() {
+ public void execute(ConstantBindingBuilder builder) {
+ builder.to(value);
+ }
+ public Object get(Object defaultValue) {
+ return value;
+ }
+ public Class getType() {
+ return Long.class;
+ }
+ @Override public String toString() {
+ return String.valueOf(value);
+ }
+ };
+ }
+
+ public void to(final boolean value) {
+ assertNoTarget();
+
+ BindConstantCommand.this.target = new ConstantTarget() {
+ public void execute(ConstantBindingBuilder builder) {
+ builder.to(value);
+ }
+ public Object get(Object defaultValue) {
+ return value;
+ }
+ public Class getType() {
+ return Boolean.class;
+ }
+ @Override public String toString() {
+ return String.valueOf(value);
+ }
+ };
+ }
+
+ public void to(final double value) {
+ assertNoTarget();
+
+ BindConstantCommand.this.target = new ConstantTarget() {
+ public void execute(ConstantBindingBuilder builder) {
+ builder.to(value);
+ }
+ public Object get(Object defaultValue) {
+ return value;
+ }
+ public Class getType() {
+ return Double.class;
+ }
+ @Override public String toString() {
+ return String.valueOf(value);
+ }
+ };
+ }
+
+ public void to(final float value) {
+ assertNoTarget();
+
+ BindConstantCommand.this.target = new ConstantTarget() {
+ public void execute(ConstantBindingBuilder builder) {
+ builder.to(value);
+ }
+ public Object get(Object defaultValue) {
+ return value;
+ }
+ public Class getType() {
+ return Float.class;
+ }
+ @Override public String toString() {
+ return String.valueOf(value);
+ }
+ };
+ }
+
+ public void to(final short value) {
+ assertNoTarget();
+
+ BindConstantCommand.this.target = new ConstantTarget() {
+ public void execute(ConstantBindingBuilder builder) {
+ builder.to(value);
+ }
+ public Object get(Object defaultValue) {
+ return value;
+ }
+ public Class getType() {
+ return Short.class;
+ }
+ @Override public String toString() {
+ return String.valueOf(value);
+ }
+ };
+ }
+
+ public void to(final char value) {
+ assertNoTarget();
+
+ BindConstantCommand.this.target = new ConstantTarget() {
+ public void execute(ConstantBindingBuilder builder) {
+ builder.to(value);
+ }
+ public Object get(Object defaultValue) {
+ return value;
+ }
+ public Class getType() {
+ return Character.class;
+ }
+ @Override public String toString() {
+ return String.valueOf(value);
+ }
+ };
+ }
+
+ public void to(final Class<?> value) {
+ assertNoTarget();
+
+ BindConstantCommand.this.target = new ConstantTarget() {
+ public void execute(ConstantBindingBuilder builder) {
+ builder.to(value);
+ }
+ public Object get(Object defaultValue) {
+ return value;
+ }
+ public Class getType() {
+ return Class.class;
+ }
+ @Override public String toString() {
+ return String.valueOf(value);
+ }
+ };
+ }
+
+ public <E extends Enum<E>> void to(final E value) {
+ Objects.nonNull(value, "value");
+ assertNoTarget();
+
+ BindConstantCommand.this.target = new ConstantTarget() {
+ public void execute(ConstantBindingBuilder builder) {
+ builder.to(value);
+ }
+ public Object get(Object defaultValue) {
+ return value;
+ }
+ public Class getType() {
+ return value.getDeclaringClass();
+ }
+ @Override public String toString() {
+ return String.valueOf(value);
+ }
+ };
+ }
+ }
+}
diff --git a/extensions/commands/src/com/google/inject/commands/BindInterceptorCommand.java b/extensions/commands/src/com/google/inject/commands/BindInterceptorCommand.java
new file mode 100644
index 0000000..ad12345
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/BindInterceptorCommand.java
@@ -0,0 +1,61 @@
+/**
+ * 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.inject.matcher.Matcher;
+import org.aopalliance.intercept.MethodInterceptor;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import static java.util.Collections.unmodifiableList;
+import java.util.List;
+
+/**
+ * Immutable snapshot of a request to bind an interceptor.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class BindInterceptorCommand implements Command {
+ private final Matcher<? super Class<?>> classMatcher;
+ private final Matcher<? super Method> methodMatcher;
+ private final List<MethodInterceptor> interceptors;
+
+ BindInterceptorCommand(
+ Matcher<? super Class<?>> classMatcher,
+ Matcher<? super Method> methodMatcher,
+ MethodInterceptor[] interceptors) {
+ this.classMatcher = classMatcher;
+ this.methodMatcher = methodMatcher;
+ this.interceptors = unmodifiableList(Arrays.asList(interceptors.clone()));
+ }
+
+ 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/extensions/commands/src/com/google/inject/commands/BindScopeCommand.java b/extensions/commands/src/com/google/inject/commands/BindScopeCommand.java
new file mode 100644
index 0000000..639f2c5
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/BindScopeCommand.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.commands;
+
+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 BindScopeCommand implements Command {
+ private final Class<? extends Annotation> annotationType;
+ private final Scope scope;
+
+ BindScopeCommand(
+ Class<? extends Annotation> annotationType, Scope scope) {
+ this.annotationType = annotationType;
+ this.scope = scope;
+ }
+
+ 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/extensions/commands/src/com/google/inject/commands/BindScoping.java b/extensions/commands/src/com/google/inject/commands/BindScoping.java
new file mode 100644
index 0000000..52e162d
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/BindScoping.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.commands;
+
+import com.google.inject.Scope;
+import com.google.inject.binder.ScopedBindingBuilder;
+
+import java.lang.annotation.Annotation;
+
+
+/**
+ * Immutable snapshot of a binding scope.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public interface BindScoping {
+ void execute(ScopedBindingBuilder scopedBindingBuilder);
+ boolean isEagerSingleton();
+ Scope getScope(Scope defaultValue);
+ Class<? extends Annotation> getScopeAnnotation(Class<? extends Annotation> defaultValue);
+}
diff --git a/extensions/commands/src/com/google/inject/commands/BindTarget.java b/extensions/commands/src/com/google/inject/commands/BindTarget.java
new file mode 100644
index 0000000..99bf418
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/BindTarget.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.commands;
+
+import com.google.inject.Key;
+import com.google.inject.Provider;
+import com.google.inject.binder.ConstantBindingBuilder;
+import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.binder.ScopedBindingBuilder;
+
+
+/**
+ * A binding target, which provides instances from a specific key.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public interface BindTarget<T> {
+
+ /**
+ * Execute this target against the linked binding builder.
+ */
+ ScopedBindingBuilder execute(LinkedBindingBuilder<T> linkedBindingBuilder);
+
+ /**
+ * Execute this target against the constant binding builder.
+ */
+ void execute(ConstantBindingBuilder builder);
+
+ /**
+ * Returns the bound instance, if it exists, or {@code defaultValue}
+ * if no bound value exists.
+ */
+ T get(T defaultValue);
+
+ Provider<? extends T> getProvider(Provider<? extends T> defaultValue);
+
+ Key<? extends Provider<? extends T>> getProviderKey(Key<Provider<? extends T>> defaultValue);
+
+ Key<? extends T> getKey(Key<? extends T> defaultValue);
+}
diff --git a/extensions/commands/src/com/google/inject/commands/Command.java b/extensions/commands/src/com/google/inject/commands/Command.java
new file mode 100644
index 0000000..15c1729
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/Command.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.commands;
+
+/**
+ * Immutable snapshot of a binding command.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public interface Command {
+ <T> T acceptVisitor(Visitor<T> visitor);
+
+ /**
+ * Visit commands.
+ */
+ public interface Visitor<V> {
+ V visitAddMessageError(AddMessageErrorCommand command);
+ V visitAddError(AddThrowableErrorCommand command);
+ V visitBindInterceptor(BindInterceptorCommand command);
+ V visitBindScope(BindScopeCommand command);
+ V visitRequestStaticInjection(RequestStaticInjectionCommand command);
+ V visitBindConstant(BindConstantCommand command);
+ V visitConvertToTypes(ConvertToTypesCommand command);
+ <T> V visitBind(BindCommand<T> command);
+ <T> V visitGetProvider(GetProviderCommand<T> command);
+ }
+}
diff --git a/extensions/commands/src/com/google/inject/commands/CommandRecorder.java b/extensions/commands/src/com/google/inject/commands/CommandRecorder.java
new file mode 100644
index 0000000..9c3ed9b
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/CommandRecorder.java
@@ -0,0 +1,142 @@
+/**
+ * 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.inject.*;
+import com.google.inject.binder.AnnotatedBindingBuilder;
+import com.google.inject.binder.AnnotatedConstantBindingBuilder;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.spi.TypeConverter;
+import org.aopalliance.intercept.MethodInterceptor;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Records commands executed by a module so they can be inspected or
+ * {@link CommandReplayer replayed}.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class CommandRecorder {
+ private final Stage stage = Stage.DEVELOPMENT;
+ private final EarlyRequestsProvider earlyRequestsProvider;
+
+ /**
+ * @param earlyRequestsProvider satisfies requests to
+ * {@link Binder#getProvider} at module execution time. For modules that
+ * will be used to create an injector, use {@link FutureInjector}.
+ */
+ public CommandRecorder(EarlyRequestsProvider earlyRequestsProvider) {
+ this.earlyRequestsProvider = earlyRequestsProvider;
+ }
+
+ /**
+ * Records the commands executed by {@code modules}.
+ */
+ public List<Command> recordCommands(Module... modules) {
+ return recordCommands(Arrays.asList(modules));
+ }
+
+ /**
+ * Records the commands executed by {@code modules}.
+ */
+ public List<Command> recordCommands(Iterable<Module> modules) {
+ RecordingBinder binder = new RecordingBinder();
+ for (Module module : modules) {
+ module.configure(binder);
+ }
+ return Collections.unmodifiableList(binder.commands);
+ }
+
+ private class RecordingBinder implements Binder {
+ private final List<Command> commands = new ArrayList<Command>();
+
+ public void bindInterceptor(
+ Matcher<? super Class<?>> classMatcher,
+ Matcher<? super Method> methodMatcher,
+ MethodInterceptor... interceptors) {
+ commands.add(new BindInterceptorCommand(classMatcher, methodMatcher, interceptors));
+ }
+
+ public void bindScope(Class<? extends Annotation> annotationType, Scope scope) {
+ commands.add(new BindScopeCommand(annotationType, scope));
+ }
+
+ public void requestStaticInjection(Class<?>... types) {
+ commands.add(new RequestStaticInjectionCommand(types));
+ }
+
+ public void install(Module module) {
+ module.configure(this);
+ }
+
+ public Stage currentStage() {
+ return stage;
+ }
+
+ public void addError(String message, Object... arguments) {
+ commands.add(new AddMessageErrorCommand(message, arguments));
+ }
+
+ public void addError(Throwable t) {
+ commands.add(new AddThrowableErrorCommand(t));
+ }
+
+ public <T> BindCommand<T>.BindingBuilder bind(Key<T> key) {
+ BindCommand<T> bindCommand = new BindCommand<T>(key);
+ commands.add(bindCommand);
+ return bindCommand.bindingBuilder();
+ }
+
+ 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() {
+ BindConstantCommand bindConstantCommand = new BindConstantCommand();
+ commands.add(bindConstantCommand);
+ return bindConstantCommand.bindingBuilder();
+ }
+
+ public <T> Provider<T> getProvider(final Key<T> key) {
+ commands.add(new GetProviderCommand<T>(key, earlyRequestsProvider));
+ return new Provider<T>() {
+ public T get() {
+ return earlyRequestsProvider.get(key);
+ }
+ };
+ }
+
+ public <T> Provider<T> getProvider(Class<T> type) {
+ return getProvider(Key.get(type));
+ }
+
+ public void convertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
+ TypeConverter converter) {
+ commands.add(new ConvertToTypesCommand(typeMatcher, converter));
+ }
+ }
+}
diff --git a/extensions/commands/src/com/google/inject/commands/CommandReplayer.java b/extensions/commands/src/com/google/inject/commands/CommandReplayer.java
new file mode 100644
index 0000000..4b06890
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/CommandReplayer.java
@@ -0,0 +1,164 @@
+/**
+ * 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.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.Module;
+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 org.aopalliance.intercept.MethodInterceptor;
+
+import java.util.List;
+
+/**
+ * Executes commands against a binder.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class CommandReplayer {
+
+ /**
+ * Returns a module that executes the specified commands
+ * using this executing visitor.
+ */
+ public Module createModule(final Iterable<Command> commands) {
+ return new Module() {
+ public void configure(Binder binder) {
+ replay(binder, commands);
+ }
+ };
+ }
+
+ /**
+ * Replays {@code commands} against {@code binder}.
+ */
+ public void replay(final Binder binder, Iterable<Command> commands) {
+ Objects.nonNull(binder, "binder");
+ Objects.nonNull(commands, "commands");
+
+ Command.Visitor<Void> visitor = new Command.Visitor<Void>() {
+ public Void visitAddMessageError(AddMessageErrorCommand command) {
+ replayAddMessageError(binder, command);
+ return null;
+ }
+
+ public Void visitAddError(AddThrowableErrorCommand command) {
+ replayAddError(binder, command);
+ return null;
+ }
+
+ public Void visitBindInterceptor(BindInterceptorCommand command) {
+ replayBindInterceptor(binder, command);
+ return null;
+ }
+
+ public Void visitBindScope(BindScopeCommand command) {
+ replayBindScope(binder, command);
+ return null;
+ }
+
+ public Void visitRequestStaticInjection(RequestStaticInjectionCommand command) {
+ replayRequestStaticInjection(binder, command);
+ return null;
+ }
+
+ public Void visitBindConstant(BindConstantCommand command) {
+ replayBindConstant(binder, command);
+ return null;
+ }
+
+ public Void visitConvertToTypes(ConvertToTypesCommand command) {
+ replayConvertToTypes(binder, command);
+ return null;
+ }
+
+ public <T> Void visitBind(BindCommand<T> command) {
+ replayBind(binder, command);
+ return null;
+ }
+
+ public <T> Void visitGetProvider(GetProviderCommand<T> command) {
+ replayGetProvider(binder, command);
+ return null;
+ }
+ };
+
+ for (Command command : commands) {
+ command.acceptVisitor(visitor);
+ }
+ }
+
+ public void replayAddMessageError(Binder binder, AddMessageErrorCommand command) {
+ binder.addError(command.getMessage(), command.getArguments().toArray());
+ }
+
+ public void replayAddError(Binder binder, AddThrowableErrorCommand command) {
+ binder.addError(command.getThrowable());
+ }
+
+ public void replayBindInterceptor(Binder binder, BindInterceptorCommand command) {
+ List<MethodInterceptor> interceptors = command.getInterceptors();
+ binder.bindInterceptor(command.getClassMatcher(), command.getMethodMatcher(),
+ interceptors.toArray(new MethodInterceptor[interceptors.size()]));
+ }
+
+ public void replayBindScope(Binder binder, BindScopeCommand command) {
+ binder.bindScope(command.getAnnotationType(), command.getScope());
+ }
+
+ public void replayRequestStaticInjection(Binder binder, RequestStaticInjectionCommand command) {
+ List<Class> types = command.getTypes();
+ binder.requestStaticInjection(types.toArray(new Class[types.size()]));
+ }
+
+ public void replayBindConstant(Binder binder, BindConstantCommand command) {
+ AnnotatedConstantBindingBuilder constantBindingBuilder = binder.bindConstant();
+
+ Key<Object> key = command.getKey();
+ ConstantBindingBuilder builder = key.getAnnotation() != null
+ ? constantBindingBuilder.annotatedWith(key.getAnnotation())
+ : constantBindingBuilder.annotatedWith(key.getAnnotationType());
+
+ command.getTarget().execute(builder);
+ }
+
+ public void replayConvertToTypes(Binder binder, ConvertToTypesCommand command) {
+ binder.convertToTypes(command.getTypeMatcher(), command.getTypeConverter());
+ }
+
+ public <T> void replayBind(Binder binder, BindCommand<T> command) {
+ LinkedBindingBuilder<T> lbb = binder.bind(command.getKey());
+
+ BindTarget<T> bindTarget = command.getTarget();
+ ScopedBindingBuilder sbb = bindTarget != null
+ ? bindTarget.execute(lbb)
+ : lbb;
+
+ BindScoping scoping = command.getScoping();
+ if (scoping != null) {
+ scoping.execute(sbb);
+ }
+ }
+
+ public <T> void replayGetProvider(Binder binder, GetProviderCommand<T> command) {
+ binder.getProvider(command.getKey());
+ }
+}
diff --git a/extensions/commands/src/com/google/inject/commands/ConvertToTypesCommand.java b/extensions/commands/src/com/google/inject/commands/ConvertToTypesCommand.java
new file mode 100644
index 0000000..a02536e
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/ConvertToTypesCommand.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.commands;
+
+import com.google.inject.TypeLiteral;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.spi.TypeConverter;
+
+
+/**
+ * Immutable snapshot of a request to convert binder types.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class ConvertToTypesCommand implements Command {
+ private final Matcher<? super TypeLiteral<?>> typeMatcher;
+ private final TypeConverter typeConverter;
+
+ ConvertToTypesCommand(Matcher<? super TypeLiteral<?>> typeMatcher,
+ TypeConverter typeConverter) {
+ this.typeMatcher = typeMatcher;
+ this.typeConverter = typeConverter;
+ }
+
+ 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/extensions/commands/src/com/google/inject/commands/EarlyRequestsProvider.java b/extensions/commands/src/com/google/inject/commands/EarlyRequestsProvider.java
new file mode 100644
index 0000000..bf3f4c3
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/EarlyRequestsProvider.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.commands;
+
+import com.google.inject.Key;
+
+/**
+ * Satisfies requests of the binder.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public interface EarlyRequestsProvider {
+ public <T> T get(Key<T> key);
+}
diff --git a/extensions/commands/src/com/google/inject/commands/FutureInjector.java b/extensions/commands/src/com/google/inject/commands/FutureInjector.java
new file mode 100644
index 0000000..e846ec1
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/FutureInjector.java
@@ -0,0 +1,60 @@
+/**
+ * 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.inject.Injector;
+import com.google.inject.Key;
+
+
+/**
+ * Satisfies binding requests using an eventually-created Injector. To use:
+ *
+ * <ol><li>Record commands using a {@link CommandRecorder}.</li>
+ * <li>Create an injector by replaying those commands (and possibly rewriting
+ * them) using {@link CommandReplayer}.</li>
+ * <li>Initialize the injector in the {@code FutureInjector}.</li></ol>
+ *
+ * <pre>
+ * FutureInjector futureInjector = new FutureInjector();
+ * List<Command> commands = new CommandRecorder(futureInjector).recordCommands(modules);
+ * Module module = new CommandRewriter().createModule(commands);
+ * Injector injector = Guice.createInjector(module);
+ * futureInjector.initialize(injector);
+ * </pre>
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class FutureInjector implements EarlyRequestsProvider {
+ /** manually initialized later */
+ private Injector injector;
+
+ public void initialize(Injector injector) {
+ if (this.injector != null) {
+ throw new IllegalStateException("Already initialized");
+ }
+
+ this.injector = injector;
+ }
+
+ public <T> T get(Key<T> key) {
+ if (injector == null) {
+ throw new IllegalStateException("Not yet initialized");
+ }
+
+ return injector.getInstance(key);
+ }
+}
diff --git a/extensions/commands/src/com/google/inject/commands/GetProviderCommand.java b/extensions/commands/src/com/google/inject/commands/GetProviderCommand.java
new file mode 100644
index 0000000..e155cd7
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/GetProviderCommand.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.commands;
+
+import com.google.inject.Key;
+
+/**
+ * Immutable snapshot of a request for a provider.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class GetProviderCommand<T> implements Command {
+ private final Key<T> key;
+ private final EarlyRequestsProvider earlyRequestsProvider;
+
+ GetProviderCommand(Key<T> key, EarlyRequestsProvider earlyRequestsProvider) {
+ this.key = key;
+ this.earlyRequestsProvider = earlyRequestsProvider;
+ }
+
+ public Key<T> getKey() {
+ return key;
+ }
+
+ public <T> T acceptVisitor(Visitor<T> visitor) {
+ return visitor.visitGetProvider(this);
+ }
+
+ public EarlyRequestsProvider getEarlyRequestsProvider() {
+ return earlyRequestsProvider;
+ }
+}
diff --git a/extensions/commands/src/com/google/inject/commands/RequestStaticInjectionCommand.java b/extensions/commands/src/com/google/inject/commands/RequestStaticInjectionCommand.java
new file mode 100644
index 0000000..677bd34
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/RequestStaticInjectionCommand.java
@@ -0,0 +1,42 @@
+/**
+ * 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 java.util.Arrays;
+import static java.util.Collections.unmodifiableList;
+import java.util.List;
+
+/**
+ * Immutable snapshot of a request for static injection.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class RequestStaticInjectionCommand implements Command {
+ private final List<Class> types;
+
+ RequestStaticInjectionCommand(Class[] types) {
+ this.types = unmodifiableList(Arrays.asList(types.clone()));
+ }
+
+ public List<Class> getTypes() {
+ return types;
+ }
+
+ public <T> T acceptVisitor(Visitor<T> visitor) {
+ return visitor.visitRequestStaticInjection(this);
+ }
+}
diff --git a/extensions/commands/src/com/google/inject/commands/intercepting/InterceptingInjectorBuilder.java b/extensions/commands/src/com/google/inject/commands/intercepting/InterceptingInjectorBuilder.java
new file mode 100644
index 0000000..0214355
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/intercepting/InterceptingInjectorBuilder.java
@@ -0,0 +1,211 @@
+/**
+ * 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.intercepting;
+
+import com.google.inject.*;
+import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.binder.ScopedBindingBuilder;
+import static com.google.inject.internal.Objects.nonNull;
+import com.google.inject.commands.*;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.util.*;
+
+/**
+ * Builds an {@link Injector} that intercepts provision.
+ *
+ * <h3>Limitations of the current implementation</h3>
+ *
+ * <p>All intercepted bindings must have binding targets - for example, a type
+ * that is bound to itself cannot be intercepted:
+ * <pre class="code">bind(MyServiceClass.class);</pre>
+ *
+ * <p>All intercepted bindings must be bound explicitly. Interception cannot
+ * be applied to implicit bindings, or bindings that depend on
+ * {@literal @}{@link ProvidedBy}, {@literal @}{@link ImplementedBy}
+ * annotations.
+ *
+ * <p><strong>Implementation note:</strong> To intercept provision, an
+ * additional, internal binding is created for each intercepted key. This is
+ * used to bind the original (non-intercepted) provisioning strategy, and an
+ * intercepting binding is created for the original key. This shouldn't have
+ * any side-effects on the behaviour of the injector, but may confuse tools
+ * that depend on {@link Injector#getBindings()} and similar methods.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ * @author jmourits@google.com (Jerome Mourits)
+ */
+public final class InterceptingInjectorBuilder {
+
+ private static final Key<ProvisionInterceptor> INJECTION_INTERCEPTOR_KEY
+ = Key.get(ProvisionInterceptor.class);
+
+ private final Collection<Module> modules = new ArrayList<Module>();
+ private final Set<Key<?>> keysToIntercept = new HashSet<Key<?>>();
+ private boolean tolerateUnmatchedInterceptions = false;
+
+ public InterceptingInjectorBuilder install(Module... modules) {
+ this.modules.addAll(Arrays.asList(modules));
+ return this;
+ }
+
+ public InterceptingInjectorBuilder install(Collection<Module> modules) {
+ this.modules.addAll(modules);
+ return this;
+ }
+
+ public InterceptingInjectorBuilder intercept(Key<?>... keys) {
+ this.keysToIntercept.addAll(Arrays.asList(keys));
+ return this;
+ }
+
+ public InterceptingInjectorBuilder intercept(Collection<Key<?>> keys) {
+ if (keys.contains(INJECTION_INTERCEPTOR_KEY)) {
+ throw new IllegalArgumentException("Cannot intercept the interceptor!");
+ }
+
+ keysToIntercept.addAll(keys);
+ return this;
+ }
+
+ public InterceptingInjectorBuilder intercept(Class<?>... classes) {
+ List<Key<?>> keysAsList = new ArrayList<Key<?>>(classes.length);
+ for (Class<?> clas : classes) {
+ keysAsList.add(Key.get(clas));
+ }
+
+ return intercept(keysAsList);
+ }
+
+ public InterceptingInjectorBuilder tolerateUnmatchedInterceptions() {
+ this.tolerateUnmatchedInterceptions = true;
+ return this;
+ }
+
+ public Injector build() {
+ FutureInjector futureInjector = new FutureInjector();
+
+ // record commands from the modules
+ List<Command> commands = new CommandRecorder(futureInjector).recordCommands(modules);
+
+ // rewrite the commands to insert interception
+ CommandRewriter rewriter = new CommandRewriter();
+ Module module = rewriter.createModule(commands);
+
+ // create and injector with the rewritten commands
+ Injector injector = Guice.createInjector(module);
+
+ // fail if any interceptions were missing
+ if (!tolerateUnmatchedInterceptions
+ && !rewriter.keysIntercepted.equals(keysToIntercept)) {
+ Set<Key> keysNotIntercepted = new HashSet<Key>(keysToIntercept);
+ keysNotIntercepted.removeAll(rewriter.keysIntercepted);
+ throw new IllegalArgumentException("An explicit binding is required for "
+ + "all intercepted keys, but was not found for " + keysNotIntercepted);
+ }
+
+ // make the injector available for callbacks from early providers
+ futureInjector.initialize(injector);
+
+ return injector;
+ }
+
+ /**
+ * Replays commands, inserting the InterceptingProvider where necessary.
+ */
+ private class CommandRewriter extends CommandReplayer {
+ private Set<Key> keysIntercepted = new HashSet<Key>();
+
+ @Override public <T> void replayBind(Binder binder, BindCommand<T> command) {
+ Key<T> key = command.getKey();
+
+ if (!keysToIntercept.contains(key)) {
+ super.replayBind(binder, command);
+ return;
+ }
+
+ if (command.getTarget() == null) {
+ throw new UnsupportedOperationException(
+ String.format("Cannot intercept bare binding of %s.", key));
+ }
+
+ Key<T> anonymousKey = Key.get(key.getTypeLiteral(), uniqueAnnotation());
+ binder.bind(key).toProvider(new InterceptingProvider<T>(key, anonymousKey));
+
+ LinkedBindingBuilder<T> linkedBindingBuilder = binder.bind(anonymousKey);
+ ScopedBindingBuilder scopedBindingBuilder = command.getTarget().execute(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);
+ }
+
+ keysIntercepted.add(key);
+ }
+ }
+
+ /**
+ * Provide {@code T}, with a hook for an {@link ProvisionInterceptor}.
+ */
+ private static class InterceptingProvider<T> implements Provider<T> {
+ private final Key<T> key;
+ private final Key<T> anonymousKey;
+ private Provider<ProvisionInterceptor> injectionInterceptorProvider;
+ private Provider<? extends T> delegateProvider;
+
+ public InterceptingProvider(Key<T> key, Key<T> anonymousKey) {
+ this.key = key;
+ this.anonymousKey = anonymousKey;
+ }
+
+ @Inject void initialize(Injector injector,
+ Provider<ProvisionInterceptor> injectionInterceptorProvider) {
+ this.injectionInterceptorProvider = nonNull(
+ injectionInterceptorProvider, "injectionInterceptorProvider");
+ this.delegateProvider = nonNull(
+ injector.getProvider(anonymousKey), "delegateProvider");
+ }
+
+ public T get() {
+ nonNull(injectionInterceptorProvider, "injectionInterceptorProvider");
+ nonNull(delegateProvider, "delegateProvider");
+ return injectionInterceptorProvider.get().intercept(key, delegateProvider);
+ }
+ }
+
+ /**
+ * Returns an annotation instance that is not equal to any other annotation
+ * instances, for use in creating distinct {@link Key}s.
+ */
+ private static Annotation uniqueAnnotation() {
+ return new Annotation() {
+ public Class<? extends Annotation> annotationType() {
+ return Internal.class;
+ }
+ @Override public String toString() {
+ return "InterceptingBinderPrivate";
+ }
+ };
+ }
+ @Retention(RUNTIME) @BindingAnnotation
+ private @interface Internal { }
+}
diff --git a/extensions/commands/src/com/google/inject/commands/intercepting/ProvisionInterceptor.java b/extensions/commands/src/com/google/inject/commands/intercepting/ProvisionInterceptor.java
new file mode 100644
index 0000000..dc8737d
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/intercepting/ProvisionInterceptor.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.commands.intercepting;
+
+import com.google.inject.Key;
+import com.google.inject.Provider;
+
+/**
+ * Intercepts object provision.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ * @author jmourits@google.com (Jerome Mourits)
+ */
+public interface ProvisionInterceptor {
+ <T> T intercept(Key<T> key, Provider<? extends T> delegate);
+}
diff --git a/extensions/commands/src/com/google/inject/commands/intercepting/package-info.java b/extensions/commands/src/com/google/inject/commands/intercepting/package-info.java
new file mode 100644
index 0000000..e7fdfb9
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/intercepting/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Extension for intercepting object provision.
+ */
+package com.google.inject.commands.intercepting;
\ No newline at end of file
diff --git a/extensions/commands/src/com/google/inject/commands/package-info.java b/extensions/commands/src/com/google/inject/commands/package-info.java
new file mode 100644
index 0000000..c4fd670
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/commands/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Extension for recording, reviewing and instrumenting the commands executed
+ * by a module.
+ */
+package com.google.inject.commands;
\ No newline at end of file
diff --git a/extensions/commands/src/com/google/inject/injectioncontroller/InjectionController.java b/extensions/commands/src/com/google/inject/injectioncontroller/InjectionController.java
new file mode 100644
index 0000000..b78f169
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/injectioncontroller/InjectionController.java
@@ -0,0 +1,144 @@
+/**
+ * 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.injectioncontroller;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.commands.intercepting.ProvisionInterceptor;
+import com.google.inject.internal.Objects;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Allows bound objects to be substituted at runtime. To use:
+ *
+ * <ol><li>Create a module that binds {@link ProvisionInterceptor} to the
+ * result of {@link #getProvisionInterceptor()} in the desired scope. Or use
+ * {@link #createModule()} which binds it with no scope.</li>
+ * <li>Create an {@code InterceptingInjectorBuilder} that installs your
+ * application modules, plus the module from the previous step.</li>
+ * <li>Configure the builder to intercept each type that you would like to
+ * substitute.</li>
+ * <li>Build the injector. Whenever the injector needs an instance of a
+ * controlled type, the injection controller will disregard that binding if a
+ * value for that type as been substituted.</li></ul>
+ *
+ * <pre>
+ * InjectionController injectionController = new InjectionController();
+ *
+ * Injector injector = new InterceptingInjectorBuilder()
+ * .install(new MyApplicationModule(), injectionController.createModule());
+ * .intercept(PersistenceEngine.class)
+ * .intercept(DeliveryRequestService.class)
+ * .build();
+ *
+ * injectionController.substitute(PersistenceEngine.class, new MockPersistenceEngine());
+ * </pre>
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ * @author jmourits@google.com (Jerome Mourits)
+ */
+public class InjectionController {
+ private final Map<Key<?>, Object> mapWritable = new HashMap<Key<?>, Object>();
+ private final Map<Key<?>, Object> map = Collections.unmodifiableMap(mapWritable);
+
+ private final ProvisionInterceptor provisionInterceptor = new ProvisionInterceptor() {
+ @SuppressWarnings({"unchecked"})
+ public <T> T intercept(Key<T> key, Provider<? extends T> delegate) {
+ T mockT = (T) map.get(key);
+ return (mockT == null)
+ // This always happens in production
+ ? delegate.get()
+ // This will happen when running tests that "control" a <T>'s injection
+ : mockT;
+ }
+ };
+
+ /**
+ * Returns the injection interceptor for binding
+ */
+ public ProvisionInterceptor getProvisionInterceptor() {
+ return provisionInterceptor;
+ }
+
+ /**
+ * Returns a module that binds the provision interceptor without a scope.
+ */
+ public final Module createModule() {
+ return new AbstractModule() {
+ protected void configure() {
+ bind(ProvisionInterceptor.class)
+ .toInstance(provisionInterceptor);
+ }
+ };
+ }
+
+ /**
+ * Substitutes the injector's existing binding for {@code key} with
+ * {@code instance}.
+ */
+ public <T> InjectionController substitute(Key<T> key, T instance) {
+ Objects.nonNull(key, "key");
+
+ if (map.containsKey(key)) {
+ throw new IllegalStateException(key + " was already being doubled.");
+ }
+
+ mapWritable.put(key, instance);
+ return this;
+ }
+
+ /**
+ * Substitutes the injector's existing binding for {@code type} with
+ * {@code instance}.
+ */
+ public <T> InjectionController substitute(Class<T> type, T instance) {
+ return substitute(Key.get(type), instance);
+ }
+
+ /**
+ * Restores the original binding for {@code key}.
+ */
+ public <T> InjectionController remove(Key<T> key) {
+ Objects.nonNull(key, "key");
+
+ if (!map.containsKey(key)) {
+ throw new IllegalStateException(key + " was not being doubled.");
+ }
+
+ mapWritable.remove(key);
+ return this;
+ }
+
+ /**
+ * Restores the original binding for {@code type}.
+ */
+ public <T> InjectionController remove(Class<T> type) {
+ return remove(Key.get(type));
+ }
+
+ /**
+ * Returns an unmodifiable, mutable map with the substituted bindings.
+ */
+ public Map<Key<?>, Object> getSubstitutesMap() {
+ return map;
+ }
+}
diff --git a/extensions/commands/src/com/google/inject/injectioncontroller/package-info.java b/extensions/commands/src/com/google/inject/injectioncontroller/package-info.java
new file mode 100644
index 0000000..41a5b4d
--- /dev/null
+++ b/extensions/commands/src/com/google/inject/injectioncontroller/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Extension for overriding bindings.
+ */
+package com.google.inject.injectioncontroller;
\ No newline at end of file
diff --git a/extensions/commands/test/com/google/inject/commands/CommandRecorderTest.java b/extensions/commands/test/com/google/inject/commands/CommandRecorderTest.java
new file mode 100644
index 0000000..a6a566d
--- /dev/null
+++ b/extensions/commands/test/com/google/inject/commands/CommandRecorderTest.java
@@ -0,0 +1,681 @@
+/**
+ * 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.inject.*;
+import com.google.inject.binder.AnnotatedBindingBuilder;
+import com.google.inject.binder.ScopedBindingBuilder;
+import com.google.inject.binder.AnnotatedConstantBindingBuilder;
+import com.google.inject.binder.ConstantBindingBuilder;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.matcher.Matchers;
+import com.google.inject.name.Names;
+import com.google.inject.spi.TypeConverter;
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+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.*;
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class CommandRecorderTest extends TestCase {
+
+ protected EarlyRequestsProvider earlyRequestProvider = new EarlyRequestsProvider() {
+ public <T> T get(Key<T> key) {
+ throw new AssertionFailedError();
+ }
+ };
+
+ private CommandRecorder commandRecorder = new CommandRecorder(earlyRequestProvider);
+
+ public void testAddMessageErrorCommand() {
+ checkModule(
+ new AbstractModule() {
+ protected void configure() {
+ addError("Message", "A", "B", "C");
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitAddMessageError(AddMessageErrorCommand command) {
+ assertEquals(Arrays.asList("A", "B", "C"), command.getArguments());
+ assertEquals("Message", command.getMessage());
+ return null;
+ }
+ }
+ );
+ }
+
+ public void testAddThrowableErrorCommand() {
+ checkModule(
+ new AbstractModule() {
+ protected void configure() {
+ addError(new Exception("A"));
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitAddError(AddThrowableErrorCommand command) {
+ assertEquals("A", command.getThrowable().getMessage());
+ return null;
+ }
+ }
+ );
+ }
+
+ public void testBindConstantAnnotations() {
+ checkModule(
+ new AbstractModule() {
+ protected void configure() {
+ bindConstant().annotatedWith(SampleAnnotation.class).to("A");
+ bindConstant().annotatedWith(Names.named("Bee")).to("B");
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitBindConstant(BindConstantCommand command) {
+ assertEquals(Key.get(String.class, SampleAnnotation.class), command.getKey());
+ assertEquals("A", command.getTarget().get(null));
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitBindConstant(BindConstantCommand command) {
+ assertEquals(Key.get(String.class, Names.named("Bee")), command.getKey());
+ assertEquals("B", command.getTarget().get(null));
+ 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 FailingVisitor() {
+ @Override public Void visitBindConstant(BindConstantCommand command) {
+ assertEquals(Key.get(String.class, Names.named("String")), command.getKey());
+ assertEquals("A", command.getTarget().get(null));
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitBindConstant(BindConstantCommand command) {
+ assertEquals(Key.get(Integer.class, Names.named("int")), command.getKey());
+ assertEquals(2, command.getTarget().get(null));
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitBindConstant(BindConstantCommand command) {
+ assertEquals(Key.get(Long.class, Names.named("long")), command.getKey());
+ assertEquals(3L, command.getTarget().get(null));
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitBindConstant(BindConstantCommand command) {
+ assertEquals(Key.get(Boolean.class, Names.named("boolean")), command.getKey());
+ assertEquals(false, command.getTarget().get(null));
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitBindConstant(BindConstantCommand command) {
+ assertEquals(Key.get(Double.class, Names.named("double")), command.getKey());
+ assertEquals(5.0d, command.getTarget().get(null));
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitBindConstant(BindConstantCommand command) {
+ assertEquals(Key.get(Float.class, Names.named("float")), command.getKey());
+ assertEquals(6.0f, command.getTarget().get(null));
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitBindConstant(BindConstantCommand command) {
+ assertEquals(Key.get(Short.class, Names.named("short")), command.getKey());
+ assertEquals((short) 7, command.getTarget().get(null));
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitBindConstant(BindConstantCommand command) {
+ assertEquals(Key.get(Character.class, Names.named("char")), command.getKey());
+ assertEquals('h', command.getTarget().get(null));
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitBindConstant(BindConstantCommand command) {
+ assertEquals(Key.get(Class.class, Names.named("Class")), command.getKey());
+ assertEquals(Iterator.class, command.getTarget().get(null));
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitBindConstant(BindConstantCommand command) {
+ assertEquals(Key.get(CoinSide.class, Names.named("Enum")), command.getKey());
+ assertEquals(CoinSide.TAILS, command.getTarget().get(null));
+ return null;
+ }
+ }
+ );
+ }
+
+ public void testBindKeysNoAnnotations() {
+ FailingVisitor keyChecker = new FailingVisitor() {
+ @Override public Void visitBind(BindCommand 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() {
+ FailingVisitor annotationChecker = new FailingVisitor() {
+ @Override public Void visitBind(BindCommand 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() {
+ FailingVisitor annotationChecker = new FailingVisitor() {
+ @Override public Void visitBind(BindCommand 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() {
+ checkModule(
+ new AbstractModule() {
+ protected void configure() {
+ bind(String.class).toProvider(new Provider<String>() {
+ public String get() {
+ return "A";
+ }
+ });
+ bind(List.class).toProvider(ListProvider.class);
+ bind(Collection.class).toProvider(Key.get(ListProvider.class));
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public <T> Void visitBind(BindCommand<T> command) {
+ assertEquals(Key.get(String.class), command.getKey());
+ assertEquals("A", command.getTarget().getProvider(null).get());
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public <T> Void visitBind(BindCommand<T> command) {
+ assertEquals(Key.get(List.class), command.getKey());
+ assertNull(command.getTarget().get(null));
+ assertEquals(Key.get(ListProvider.class), command.getTarget().getProviderKey(null));
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public <T> Void visitBind(BindCommand<T> command) {
+ assertEquals(Key.get(Collection.class), command.getKey());
+ assertNull(command.getTarget().get(null));
+ assertEquals(Key.get(ListProvider.class), command.getTarget().getProviderKey(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 FailingVisitor() {
+ @Override public <T> Void visitBind(BindCommand<T> command) {
+ assertEquals(Key.get(List.class), command.getKey());
+ assertEquals(Key.get(ArrayList.class), command.getTarget().getKey(null));
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public <T> Void visitBind(BindCommand<T> command) {
+ assertEquals(Key.get(Map.class), command.getKey());
+ assertEquals(Key.get(new TypeLiteral<HashMap<Integer, String>>() {}), command.getTarget().getKey(null));
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public <T> Void visitBind(BindCommand<T> command) {
+ assertEquals(Key.get(Set.class), command.getKey());
+ assertEquals(Key.get(TreeSet.class, SampleAnnotation.class), command.getTarget().getKey(null));
+ return null;
+ }
+ }
+ );
+ }
+
+ public void testBindToInstance() {
+ checkModule(
+ new AbstractModule() {
+ protected void configure() {
+ bind(String.class).toInstance("A");
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public <T> Void visitBind(BindCommand<T> command) {
+ assertEquals(Key.get(String.class), command.getKey());
+ assertEquals("A", command.getTarget().get(null));
+ return null;
+ }
+ }
+ );
+ }
+
+ public void testBindInScopes() {
+ checkModule(
+ new AbstractModule() {
+ protected void configure() {
+ 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 FailingVisitor() {
+ @Override public <T> Void visitBind(BindCommand<T> command) {
+ assertEquals(Key.get(List.class), command.getKey());
+ assertEquals(Scopes.SINGLETON, command.getScoping().getScope(null));
+ assertNull(command.getScoping().getScopeAnnotation(null));
+ assertFalse(command.getScoping().isEagerSingleton());
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public <T> Void visitBind(BindCommand<T> command) {
+ assertEquals(Key.get(Map.class), command.getKey());
+ assertEquals(Singleton.class, command.getScoping().getScopeAnnotation(null));
+ assertNull(command.getScoping().getScope(null));
+ assertFalse(command.getScoping().isEagerSingleton());
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public <T> Void visitBind(BindCommand<T> command) {
+ assertEquals(Key.get(Set.class), command.getKey());
+ assertNull(command.getScoping().getScopeAnnotation(null));
+ assertNull(command.getScoping().getScope(null));
+ assertTrue(command.getScoping().isEagerSingleton());
+ 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 FailingVisitor() {
+ @Override public Void visitBindInterceptor(BindInterceptorCommand 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 FailingVisitor() {
+ @Override public Void visitBindScope(BindScopeCommand 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 FailingVisitor() {
+ @Override public Void visitConvertToTypes(ConvertToTypesCommand command) {
+ assertSame(typeConverter, command.getTypeConverter());
+ assertSame(Matchers.any(), command.getTypeMatcher());
+ return null;
+ }
+ }
+ );
+ }
+
+ public void testGetProvider() {
+ final List<Key> calls = new ArrayList<Key>();
+
+ earlyRequestProvider = new EarlyRequestsProvider() {
+ @SuppressWarnings({"unchecked"})
+ public <T> T get(Key<T> key) {
+ calls.add(key);
+ return (T) "A";
+ }
+ };
+
+ commandRecorder = new CommandRecorder(earlyRequestProvider);
+
+ checkModule(
+ new AbstractModule() {
+ protected void configure() {
+ Provider<String> keyGetProvider = getProvider(Key.get(String.class, SampleAnnotation.class));
+ assertEquals("A", keyGetProvider.get());
+ assertEquals(Key.get(String.class, SampleAnnotation.class), calls.get(0));
+
+ Provider<String> typeGetProvider = getProvider(String.class);
+ assertEquals("A", typeGetProvider.get());
+ assertEquals(Key.get(String.class), calls.get(1));
+ assertEquals(2, calls.size());
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitGetProvider(GetProviderCommand command) {
+ assertEquals(Key.get(String.class, SampleAnnotation.class), command.getKey());
+ assertEquals(earlyRequestProvider, command.getEarlyRequestsProvider());
+ return null;
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitGetProvider(GetProviderCommand command) {
+ assertEquals(Key.get(String.class), command.getKey());
+ assertEquals(earlyRequestProvider, command.getEarlyRequestsProvider());
+ return null;
+ }
+ }
+ );
+ }
+
+ public void testRequestStaticInjection() {
+ checkModule(
+ new AbstractModule() {
+ protected void configure() {
+ requestStaticInjection(ArrayList.class);
+ }
+ },
+
+ new FailingVisitor() {
+ @Override public Void visitRequestStaticInjection(RequestStaticInjectionCommand command) {
+ assertEquals(Arrays.asList(ArrayList.class), command.getTypes());
+ return null;
+ }
+ }
+ );
+ }
+
+ public void testMalformedBindCommands() {
+ CommandRecorder recorder = new CommandRecorder(earlyRequestProvider);
+
+ recorder.recordCommands(new AbstractModule() {
+ protected void configure() {
+ AnnotatedBindingBuilder<String> abb = bind(String.class);
+ abb.annotatedWith(SampleAnnotation.class);
+ try {
+ abb.annotatedWith(Names.named("A"));
+ fail();
+ } catch(IllegalStateException expected) {
+ }
+ }
+ });
+
+ recorder.recordCommands(new AbstractModule() {
+ protected void configure() {
+ AnnotatedBindingBuilder<String> abb = bind(String.class);
+ abb.toInstance("A");
+ try {
+ abb.toInstance("B");
+ fail();
+ } catch(IllegalStateException expected) {
+ }
+ }
+ });
+
+ recorder.recordCommands(new AbstractModule() {
+ protected void configure() {
+ ScopedBindingBuilder sbb = bind(List.class).to(ArrayList.class);
+ sbb.in(Scopes.NO_SCOPE);
+ try {
+ sbb.asEagerSingleton();
+ fail();
+ } catch(IllegalStateException expected) {
+ }
+ }
+ });
+ }
+
+ public void testMalformedBindConstantCommands() {
+ CommandRecorder recorder = new CommandRecorder(earlyRequestProvider);
+
+ recorder.recordCommands(new AbstractModule() {
+ protected void configure() {
+ AnnotatedConstantBindingBuilder cbb = bindConstant();
+ cbb.annotatedWith(SampleAnnotation.class);
+ try {
+ cbb.annotatedWith(Names.named("A"));
+ fail();
+ } catch(IllegalStateException expected) {
+ }
+ }
+ });
+
+ recorder.recordCommands(new AbstractModule() {
+ protected void configure() {
+ ConstantBindingBuilder cbb = bindConstant().annotatedWith(SampleAnnotation.class);
+ cbb.to("A");
+ try {
+ cbb.to("B");
+ fail();
+ } catch(IllegalStateException expected) {
+ }
+ }
+ });
+ }
+
+ /**
+ * Ensures the module performs the commands consistent with {@code visitors}.
+ */
+ protected void checkModule(Module module, Command.Visitor<?>... visitors) {
+ List<Command> commands = commandRecorder.recordCommands(module);
+
+ assertEquals(commands.size(), visitors.length);
+
+ for (int i = 0; i < visitors.length; i++) {
+ Command.Visitor<?> visitor = visitors[i];
+ Command command = commands.get(i);
+ command.acceptVisitor(visitor);
+ }
+ }
+
+ private static class ListProvider implements Provider<List> {
+ public List get() {
+ return new ArrayList();
+ }
+ }
+
+ private static class FailingVisitor implements Command.Visitor<Void> {
+ public Void visitAddMessageError(AddMessageErrorCommand command) {
+ throw new AssertionFailedError();
+ }
+
+ public Void visitAddError(AddThrowableErrorCommand command) {
+ throw new AssertionFailedError();
+ }
+
+ public Void visitBindInterceptor(BindInterceptorCommand command) {
+ throw new AssertionFailedError();
+ }
+
+ public Void visitBindScope(BindScopeCommand command) {
+ throw new AssertionFailedError();
+ }
+
+ public Void visitRequestStaticInjection(RequestStaticInjectionCommand command) {
+ throw new AssertionFailedError();
+ }
+
+ public Void visitBindConstant(BindConstantCommand command) {
+ throw new AssertionFailedError();
+ }
+
+ public Void visitConvertToTypes(ConvertToTypesCommand command) {
+ throw new AssertionFailedError();
+ }
+
+ public <T> Void visitBind(BindCommand<T> command) {
+ throw new AssertionFailedError();
+ }
+
+ public Void visitGetProvider(GetProviderCommand command) {
+ throw new AssertionFailedError();
+ }
+ }
+
+ @Retention(RUNTIME)
+ @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
+ @BindingAnnotation
+ public @interface SampleAnnotation { }
+
+ public enum CoinSide { HEADS, TAILS }
+}
diff --git a/extensions/commands/test/com/google/inject/commands/CommandReplayerTest.java b/extensions/commands/test/com/google/inject/commands/CommandReplayerTest.java
new file mode 100644
index 0000000..2ef21a1
--- /dev/null
+++ b/extensions/commands/test/com/google/inject/commands/CommandReplayerTest.java
@@ -0,0 +1,45 @@
+/**
+ * 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.inject.Module;
+
+import java.util.List;
+
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class CommandReplayerTest extends CommandRecorderTest {
+
+ protected void checkModule(Module module, Command.Visitor<?>... visitors) {
+ // get some commands to replay
+ List<Command> commands = new CommandRecorder(earlyRequestProvider).recordCommands(module);
+
+ // replay the recorded commands, and record them again!
+ List<Command> replayedCommands = new CommandRecorder(earlyRequestProvider)
+ .recordCommands(new CommandReplayer().createModule(commands));
+
+ // verify that the replayed commands are as expected
+ assertEquals(replayedCommands.size(), visitors.length);
+ for (int i = 0; i < visitors.length; i++) {
+ Command.Visitor<?> visitor = visitors[i];
+ Command command = replayedCommands.get(i);
+ command.acceptVisitor(visitor);
+ }
+ }
+}
diff --git a/extensions/commands/test/com/google/inject/commands/CommandRewriteTest.java b/extensions/commands/test/com/google/inject/commands/CommandRewriteTest.java
new file mode 100644
index 0000000..7515b4b
--- /dev/null
+++ b/extensions/commands/test/com/google/inject/commands/CommandRewriteTest.java
@@ -0,0 +1,62 @@
+/**
+ * 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.inject.*;
+import junit.framework.TestCase;
+
+import java.util.List;
+
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class CommandRewriteTest 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 commands from that module
+ CommandRecorder commandRecorder = new CommandRecorder(new FutureInjector());
+ List<Command> commands = commandRecorder.recordCommands(module);
+
+ // create a rewriter that rewrites the binding to 'Wine' with a binding to 'Beer'
+ CommandReplayer rewriter = new CommandReplayer() {
+ @Override public <T> void replayBind(Binder binder, BindCommand<T> command) {
+ if ("Wine".equals(command.getTarget().get(null))) {
+ binder.bind(CharSequence.class).toInstance("Beer");
+ } else {
+ super.replayBind(binder, command);
+ }
+ }
+ };
+
+ // create a module from the original list of commands and the rewriter
+ Module rewrittenModule = rewriter.createModule(commands);
+
+ // it all works
+ Injector injector = Guice.createInjector(rewrittenModule);
+ assertEquals("Pizza", injector.getInstance(String.class));
+ assertEquals("Beer", injector.getInstance(CharSequence.class));
+ }
+}
diff --git a/extensions/commands/test/com/google/inject/commands/intercepting/InterceptingInjectorBuilderTest.java b/extensions/commands/test/com/google/inject/commands/intercepting/InterceptingInjectorBuilderTest.java
new file mode 100644
index 0000000..367e77d
--- /dev/null
+++ b/extensions/commands/test/com/google/inject/commands/intercepting/InterceptingInjectorBuilderTest.java
@@ -0,0 +1,191 @@
+/**
+ * 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.intercepting;
+
+import com.google.inject.*;
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import java.util.Collection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.LinkedList;
+
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class InterceptingInjectorBuilderTest extends TestCase {
+
+ private final ProvisionInterceptor failingInterceptor = new ProvisionInterceptor() {
+ public <T> T intercept(Key<T> key, Provider<? extends T> delegate) {
+ throw new AssertionFailedError();
+ }
+ };
+
+ public void testInterceptProvisionInterceptor() {
+ InterceptingInjectorBuilder builder = new InterceptingInjectorBuilder();
+
+ try {
+ builder.intercept(ProvisionInterceptor.class);
+ fail();
+ } catch(IllegalArgumentException expected) {
+ }
+ }
+
+ public void testProvisionInterception() {
+ final ProvisionInterceptor interceptor = new ProvisionInterceptor() {
+ @SuppressWarnings({"unchecked"})
+ public <T> T intercept(Key<T> key, Provider<? extends T> delegate) {
+ assertEquals(Key.get(String.class), key);
+ assertEquals("A", delegate.get());
+ return (T) "B";
+ }
+ };
+
+ Module module = new AbstractModule() {
+ protected void configure() {
+ bind(String.class).toInstance("A");
+ bind(ProvisionInterceptor.class).toInstance(interceptor);
+ }
+ };
+
+ Injector injector = new InterceptingInjectorBuilder()
+ .intercept(String.class)
+ .install(module)
+ .build();
+
+ assertEquals("B", injector.getInstance(String.class));
+ }
+
+ /**
+ * The user's provider is scoped but the interceptor is not. As this testcase
+ * demonstrates, the user's provider gets called only once (in singleton
+ * scope) but the interceptor gets called for each provision.
+ */
+ public void testInterceptionIsNotScoped() {
+ final Provider<Integer> sequenceProvider = new Provider<Integer>() {
+ private int next = 100;
+ public Integer get() {
+ return next++;
+ }
+ };
+
+ final ProvisionInterceptor interceptor = new ProvisionInterceptor() {
+ private int next = 1;
+ @SuppressWarnings({"unchecked"})
+ public <T> T intercept(Key<T> key, Provider<? extends T> delegate) {
+ assertEquals(100, delegate.get());
+ return (T) new Integer(next++);
+ }
+ };
+
+ Module module = new AbstractModule() {
+ protected void configure() {
+ bind(Integer.class).toProvider(sequenceProvider).in(Scopes.SINGLETON);
+ bind(ProvisionInterceptor.class).toInstance(interceptor);
+ }
+ };
+
+ Injector injector = new InterceptingInjectorBuilder()
+ .intercept(Integer.class)
+ .install(module)
+ .build();
+
+ assertEquals(1, (int) injector.getInstance(Integer.class));
+ assertEquals(2, (int) injector.getInstance(Integer.class));
+ }
+
+ public void testInterceptionIsWhitelistedKeysOnly() {
+ final ProvisionInterceptor interceptor = new ProvisionInterceptor() {
+ @SuppressWarnings({"unchecked"})
+ public <T> T intercept(Key<T> key, Provider<? extends T> delegate) {
+ assertEquals(ArrayList.class, delegate.get().getClass());
+ return (T) new LinkedList();
+ }
+ };
+
+ Module module = new AbstractModule() {
+ protected void configure() {
+ bind(Collection.class).to(ArrayList.class);
+ bind(List.class).to(ArrayList.class);
+ bind(ProvisionInterceptor.class).toInstance(interceptor);
+ }
+ };
+
+ Injector injector = new InterceptingInjectorBuilder()
+ .intercept(List.class)
+ .install(module)
+ .build();
+
+ assertEquals(LinkedList.class, injector.getInstance(List.class).getClass());
+ assertEquals(ArrayList.class, injector.getInstance(Collection.class).getClass());
+ }
+
+ public void testCannotInterceptBareBinding() {
+ Module module = new AbstractModule() {
+ protected void configure() {
+ bind(ArrayList.class);
+ }
+ };
+
+ InterceptingInjectorBuilder builder = new InterceptingInjectorBuilder()
+ .intercept(ArrayList.class)
+ .install(module);
+
+ try {
+ builder.build();
+ fail();
+ } catch(UnsupportedOperationException expected) {
+ }
+ }
+
+ public void testAllInterceptedKeysMustBeBound() {
+ Module module = new AbstractModule() {
+ protected void configure() {
+ bind(ProvisionInterceptor.class).toInstance(failingInterceptor);
+ }
+ };
+
+ InterceptingInjectorBuilder builder = new InterceptingInjectorBuilder()
+ .intercept(ArrayList.class)
+ .install(module);
+
+ try {
+ builder.build();
+ fail();
+ } catch(IllegalArgumentException expected) {
+ }
+ }
+
+ public void testTolerateUnmatchedInterceptions() {
+ Module module = new AbstractModule() {
+ protected void configure() {
+ bind(ProvisionInterceptor.class).toInstance(failingInterceptor);
+ }
+ };
+
+ Injector injector = new InterceptingInjectorBuilder()
+ .intercept(ArrayList.class)
+ .tolerateUnmatchedInterceptions()
+ .install(module)
+ .build();
+
+ assertEquals(new ArrayList(), injector.getInstance(ArrayList.class));
+ }
+
+}
diff --git a/extensions/commands/test/com/google/inject/injectioncontroller/InjectionControllerTest.java b/extensions/commands/test/com/google/inject/injectioncontroller/InjectionControllerTest.java
new file mode 100644
index 0000000..347c682
--- /dev/null
+++ b/extensions/commands/test/com/google/inject/injectioncontroller/InjectionControllerTest.java
@@ -0,0 +1,89 @@
+/**
+ * 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.injectioncontroller;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.commands.intercepting.InterceptingInjectorBuilder;
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ * @author jmourits@google.com (Jerome Mourits)
+ */
+public class InjectionControllerTest extends TestCase {
+
+ private InjectionController injectionController = new InjectionController();
+
+ public void testSimpleOverride() throws Exception {
+ Injector injector = new InterceptingInjectorBuilder()
+ .install(injectionController.createModule(),
+ new AbstractModule() {
+ protected void configure() {
+ bind(String.class).toInstance("a");
+ }
+ })
+ .intercept(String.class)
+ .build();
+
+ assertEquals("a", injector.getInstance(String.class));
+ injectionController.substitute(String.class, "b");
+ assertEquals("b", injector.getInstance(String.class));
+ }
+
+ public void testOverrideRequiresWhitelist() throws Exception {
+ Injector injector = new InterceptingInjectorBuilder()
+ .install(injectionController.createModule(),
+ new AbstractModule() {
+ protected void configure() {
+ bind(String.class).toInstance("a");
+ }
+ })
+ .build();
+
+ injectionController.substitute(String.class, "b");
+ assertEquals("a", injector.getInstance(String.class));
+ }
+
+ public void testBareBindingFails() throws Exception {
+ InterceptingInjectorBuilder builder = new InterceptingInjectorBuilder()
+ .install(injectionController.createModule(),
+ new AbstractModule() {
+ protected void configure() {
+ bind(ArrayList.class);
+ }
+ })
+ .intercept(ArrayList.class);
+
+ try {
+ builder.build();
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
+
+ public void testCannotOverrideDouble() throws Exception {
+ injectionController.substitute(String.class, "b");
+ try {
+ injectionController.substitute(String.class, "c");
+ fail();
+ } catch(IllegalStateException expected) {
+ }
+ }
+}