Revision: 1140
Author: sberlin
Date: Fri Feb 5 13:12:05 2010
Log: issue 454 - add an annotation to mark @Injectable methods as wanting
injection during Stage.TOOL.
http://code.google.com/p/google-guice/source/detail?r=1140
Added:
/trunk/src/com/google/inject/spi/Toolable.java
/trunk/test/com/google/inject/spi/ToolStageInjectorTest.java
Modified:
/trunk/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java
/trunk/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java
/trunk/src/com/google/inject/internal/ConstructorInjector.java
/trunk/src/com/google/inject/internal/Initializer.java
/trunk/src/com/google/inject/internal/InjectionRequestProcessor.java
/trunk/src/com/google/inject/internal/InjectorBuilder.java
/trunk/src/com/google/inject/internal/InjectorImpl.java
/trunk/src/com/google/inject/internal/InjectorShell.java
/trunk/src/com/google/inject/internal/MembersInjectorImpl.java
/trunk/src/com/google/inject/spi/InjectionPoint.java
/trunk/test/com/google/inject/AllTests.java
/trunk/test/com/google/inject/InjectorTest.java
=======================================
--- /dev/null
+++ /trunk/src/com/google/inject/spi/Toolable.java Fri Feb 5 13:12:05 2010
@@ -0,0 +1,29 @@
+package com.google.inject.spi;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+
+
+/**
+ * Instructs an {...@link Injector} running in {...@link Stage#TOOL} that
+ * a method should be injected. This is typically useful for for
+ * extensions to Guice that perform additional validation in an
+ * {...@literal @}...@link Inject}ed method. This only applies to objects
+ * that are already constructed when bindings are created (ie.,
+ * something bound using toProvider, toInstance, or requestInjection).
+ *
+ * @author [email protected] (Sam Berlin)
+ */
+...@target({ METHOD })
+...@retention(RUNTIME)
+...@documented
+public @interface Toolable {
+}
=======================================
--- /dev/null
+++ /trunk/test/com/google/inject/spi/ToolStageInjectorTest.java Fri Feb 5
13:12:05 2010
@@ -0,0 +1,165 @@
+package com.google.inject.spi;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Asserts;
+import com.google.inject.CreationException;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Provider;
+import com.google.inject.Stage;
+import com.google.inject.spi.Toolable;
+
+public class ToolStageInjectorTest extends TestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ Foo.s = null;
+ Foo.sm = null;
+ }
+
+ public void testToolStageInjectorRestrictions() {
+ Injector injector = Guice.createInjector(Stage.TOOL);
+ try {
+ injector.injectMembers(new Object());
+ fail("Non-SPI Injector methods must throw an exception in the TOOL
stage.");
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ try {
+ injector.getInstance(Injector.class);
+ fail("Non-SPI Injector methods must throw an exception in the TOOL
stage.");
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ try {
+ injector.getInstance(Key.get(Injector.class));
+ fail("Non-SPI Injector methods must throw an exception in the TOOL
stage.");
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ try {
+ injector.getProvider(Injector.class);
+ fail("Non-SPI Injector methods must throw an exception in the TOOL
stage.");
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ try {
+ injector.getProvider(Key.get(Injector.class));
+ fail("Non-SPI Injector methods must throw an exception in the TOOL
stage.");
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
+
+ public void testToolStageDoesntInjectInstances() {
+ final Foo foo = new Foo();
+ Guice.createInjector(Stage.TOOL, new AbstractModule() {
+ @Override
+ protected void configure() {
+ requestStaticInjection(Foo.class);
+ requestInjection(foo);
+ }
+ });
+ assertNull(Foo.s);
+ assertNull(Foo.sm);
+ assertNull(foo.f);
+ assertNull(foo.m);
+ }
+
+ public void testToolStageDoesntInjectProviders() {
+ final Foo foo = new Foo();
+ Guice.createInjector(Stage.TOOL, new AbstractModule() {
+ @Override
+ protected void configure() {
+ requestStaticInjection(Foo.class);
+ bind(Object.class).toProvider(foo);
+ }
+ });
+ assertNull(Foo.s);
+ assertNull(Foo.sm);
+ assertNull(foo.f);
+ assertNull(foo.m);
+ }
+
+ public void testToolStageWarnsOfMissingObjectGraph() {
+ final Bar bar = new Bar();
+ try {
+ Guice.createInjector(Stage.TOOL, new AbstractModule() {
+ @Override
+ protected void configure() {
+ requestStaticInjection(Bar.class);
+ requestInjection(bar);
+ }
+ });
+ fail("expected exception");
+ } catch(CreationException expected) {
+ Asserts.assertContains(expected.toString(), "No implementation for
java.util.Collection was bound.",
+ "No implementation for java.util.Map was bound.",
+ "No implementation for java.util.List was bound.",
+ "No implementation for java.util.Set was bound.");
+ }
+ }
+
+ public void testToolStageInjectsTooledMethods() {
+ final Tooled tooled = new Tooled();
+ Guice.createInjector(Stage.TOOL, new AbstractModule() {
+ @Override
+ protected void configure() {
+ requestStaticInjection(Tooled.class);
+ bind(Object.class).toProvider(tooled);
+ }
+ });
+ assertNull(Tooled.s);
+ assertNotNull(Tooled.sm);
+ assertNull(tooled.f);
+ assertNotNull(tooled.m);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static class Bar {
+ @SuppressWarnings("unused") @Inject private static List list;
+ @SuppressWarnings("unused") @Inject private Set set;
+ @SuppressWarnings("unused") @Inject void method(Collection c) {}
+ @SuppressWarnings("unused") @Inject static void staticMethod(Map map)
{}
+ }
+
+ private static class Foo implements Provider<Object> {
+ @Inject private static S s;
+ @Inject private F f;
+ private M m;
+ @SuppressWarnings("unused") @Inject void method(M m) { this.m = m; }
+ private static SM sm;
+ @SuppressWarnings("unused") @Inject static void staticMethod(SM sm) {
Tooled.sm = sm; }
+
+ public Object get() {
+ return null;
+ }
+ }
+
+ private static class Tooled implements Provider<Object> {
+ @Inject private static S s;
+ @Inject private F f;
+ private M m;
+ @Toolable @SuppressWarnings("unused") @Inject void method(M m) {
this.m = m; }
+ private static SM sm;
+ @Toolable @SuppressWarnings("unused") @Inject static void
staticMethod(SM sm) { Tooled.sm = sm; }
+
+ public Object get() {
+ return null;
+ }
+ }
+
+ private static class S {}
+ private static class F {}
+ private static class M {}
+ private static class SM {}
+
+}
=======================================
---
/trunk/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java
Wed Aug 19 11:31:04 2009
+++
/trunk/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java
Fri Feb 5 13:12:05 2010
@@ -36,6 +36,7 @@
import com.google.inject.internal.Lists;
import static com.google.inject.internal.Preconditions.checkState;
import com.google.inject.spi.Message;
+import com.google.inject.spi.Toolable;
import com.google.inject.util.Providers;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
@@ -157,7 +158,7 @@
* At injector-creation time, we initialize the invocation handler. At
this time we make sure
* all factory methods will be able to build the target types.
*/
- @Inject
+ @Inject @Toolable
void initialize(Injector injector) {
if (this.injector != null) {
throw new ConfigurationException(ImmutableList.of(new
Message(FactoryProvider2.class,
=======================================
---
/trunk/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java
Wed Aug 19 11:18:16 2009
+++
/trunk/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java
Fri Feb 5 13:12:05 2010
@@ -26,6 +26,7 @@
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
+import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.matcher.Matchers;
import com.google.inject.name.Named;
@@ -454,6 +455,22 @@
"at " + ColoredCarFactory.class.getName()
+ ".create(FactoryProvider2Test.java");
}
}
+
+ public void testFactoryFailsWithMissingBindingInToolStage() {
+ try {
+ Guice.createInjector(Stage.TOOL, new AbstractModule() {
+ @Override protected void configure() {
+ bind(ColoredCarFactory.class).toProvider(
+ FactoryProvider.newFactory(ColoredCarFactory.class,
Mustang.class));
+ }
+ });
+ fail();
+ } catch (CreationException expected) {
+ assertContains(expected.getMessage(),
+ "Could not find a suitable constructor in java.lang.Double.",
+ "at " + ColoredCarFactory.class.getName()
+ ".create(FactoryProvider2Test.java");
+ }
+ }
public void testMethodsDeclaredInObject() {
Injector injector = Guice.createInjector(new AbstractModule() {
=======================================
--- /trunk/src/com/google/inject/internal/ConstructorInjector.java Sun Jun
7 12:37:28 2009
+++ /trunk/src/com/google/inject/internal/ConstructorInjector.java Fri Feb
5 13:12:05 2010
@@ -86,7 +86,7 @@
// Store reference. If an injector re-enters this factory, they'll
get the same reference.
constructionContext.setCurrentReference(t);
- membersInjector.injectMembers(t, errors, context);
+ membersInjector.injectMembers(t, errors, context, false);
membersInjector.notifyListeners(t, errors);
return t;
=======================================
--- /trunk/src/com/google/inject/internal/Initializer.java Sat Jun 6
10:51:27 2009
+++ /trunk/src/com/google/inject/internal/Initializer.java Fri Feb 5
13:12:05 2010
@@ -16,6 +16,7 @@
package com.google.inject.internal;
+import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import static com.google.inject.internal.Preconditions.checkNotNull;
import com.google.inject.spi.InjectionPoint;
@@ -139,7 +140,9 @@
// toInject needs injection, do it right away. we only do this once,
even if it fails
if (pendingInjection.remove(instance) != null) {
- membersInjector.injectAndNotify(instance,
errors.withSource(source));
+ // if in Stage.TOOL, we only want to inject & notify toolable
injection points.
+ // (otherwise we'll inject all of them)
+ membersInjector.injectAndNotify(instance,
errors.withSource(source), injector.stage == Stage.TOOL);
}
return instance;
=======================================
--- /trunk/src/com/google/inject/internal/InjectionRequestProcessor.java
Mon Sep 28 21:32:55 2009
+++ /trunk/src/com/google/inject/internal/InjectionRequestProcessor.java
Fri Feb 5 13:12:05 2010
@@ -17,6 +17,7 @@
package com.google.inject.internal;
import com.google.inject.ConfigurationException;
+import com.google.inject.Stage;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.InjectionRequest;
import com.google.inject.spi.StaticInjectionRequest;
@@ -106,8 +107,12 @@
try {
injector.callInContext(new ContextualCallable<Void>() {
public Void call(InternalContext context) {
- for (SingleMemberInjector injector : memberInjectors) {
- injector.inject(errors, context, null);
+ for (SingleMemberInjector memberInjector : memberInjectors) {
+ // Run injections if we're not in tool stage (ie, PRODUCTION
or DEV),
+ // or if we are in tool stage and the injection point is
toolable.
+ if(injector.stage != Stage.TOOL ||
memberInjector.getInjectionPoint().isToolable()) {
+ memberInjector.inject(errors, context, null);
+ }
}
return null;
}
=======================================
--- /trunk/src/com/google/inject/internal/InjectorBuilder.java Thu Jul 23
17:48:12 2009
+++ /trunk/src/com/google/inject/internal/InjectorBuilder.java Fri Feb 5
13:12:05 2010
@@ -102,20 +102,21 @@
// Synchronize while we're building up the bindings and other injector
state. This ensures that
// the JIT bindings in the parent injector don't change while we're
being built
synchronized (shellBuilder.lock()) {
- shells = shellBuilder.build(initializer, bindingProcesor, stopwatch,
errors);
+ shells = shellBuilder.build(bindingProcesor, stopwatch, errors);
stopwatch.resetAndLog("Injector construction");
initializeStatically();
}
- // If we're in the tool stage, stop here. Don't eagerly inject or load
anything.
+ injectDynamically();
+
if (stage == Stage.TOOL) {
+ // wrap the primaryInjector in a ToolStageInjector
+ // to prevent non-tool-friendy methods from being called.
return new ToolStageInjector(primaryInjector());
- }
-
- injectDynamically();
-
- return primaryInjector();
+ } else {
+ return primaryInjector();
+ }
}
/** Initialize and validate everything. */
@@ -175,10 +176,12 @@
stopwatch.resetAndLog("Instance injection");
errors.throwCreationExceptionIfErrorsExist();
- for (InjectorShell shell : shells) {
- loadEagerSingletons(shell.getInjector(), stage, errors);
- }
- stopwatch.resetAndLog("Preloading singletons");
+ if(stage != Stage.TOOL) {
+ for (InjectorShell shell : shells) {
+ loadEagerSingletons(shell.getInjector(), stage, errors);
+ }
+ stopwatch.resetAndLog("Preloading singletons");
+ }
errors.throwCreationExceptionIfErrorsExist();
}
=======================================
--- /trunk/src/com/google/inject/internal/InjectorImpl.java Mon Nov 23
12:24:31 2009
+++ /trunk/src/com/google/inject/internal/InjectorImpl.java Fri Feb 5
13:12:05 2010
@@ -28,6 +28,7 @@
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Scope;
+import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.ConvertedConstantBinding;
@@ -58,17 +59,17 @@
final State state;
final InjectorImpl parent;
final BindingsMultimap bindingsMultimap = new BindingsMultimap();
- final Initializer initializer;
+ final Stage stage;
/** Just-in-time binding cache. Guarded by state.lock() */
final Map<Key<?>, BindingImpl<?>> jitBindings = Maps.newHashMap();
Lookups lookups = new DeferredLookups(this);
- InjectorImpl(@Nullable InjectorImpl parent, State state, Initializer
initializer) {
+ InjectorImpl(@Nullable InjectorImpl parent, State state, Stage stage) {
this.parent = parent;
this.state = state;
- this.initializer = initializer;
+ this.stage = stage;
if (parent != null) {
localContext = parent.localContext;
=======================================
--- /trunk/src/com/google/inject/internal/InjectorShell.java Mon Sep 7
15:59:32 2009
+++ /trunk/src/com/google/inject/internal/InjectorShell.java Fri Feb 5
13:12:05 2010
@@ -107,13 +107,13 @@
* returned if any modules contain {...@link Binder#newPrivateBinder
private environments}. The
* primary injector will be first in the returned list.
*/
- List<InjectorShell> build(Initializer initializer, BindingProcessor
bindingProcessor,
+ List<InjectorShell> build(BindingProcessor bindingProcessor,
Stopwatch stopwatch, Errors errors) {
checkState(stage != null, "Stage not initialized");
checkState(privateElements == null || parent !=
null, "PrivateElements with no parent");
checkState(state != null, "no state. Did you remember to lock() ?");
- InjectorImpl injector = new InjectorImpl(parent, state, initializer);
+ InjectorImpl injector = new InjectorImpl(parent, state, stage);
if (privateElements != null) {
privateElements.initInjector(injector);
}
@@ -158,7 +158,7 @@
PrivateElementProcessor processor = new
PrivateElementProcessor(errors, stage);
processor.process(injector, elements);
for (Builder builder : processor.getInjectorShellBuilders()) {
- injectorShells.addAll(builder.build(initializer, bindingProcessor,
stopwatch, errors));
+ injectorShells.addAll(builder.build(bindingProcessor, stopwatch,
errors));
}
stopwatch.resetAndLog("Private environment creation");
=======================================
--- /trunk/src/com/google/inject/internal/MembersInjectorImpl.java Sat Jun
6 10:51:27 2009
+++ /trunk/src/com/google/inject/internal/MembersInjectorImpl.java Fri Feb
5 13:12:05 2010
@@ -17,6 +17,7 @@
package com.google.inject.internal;
import com.google.inject.MembersInjector;
+import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.InjectionListener;
import com.google.inject.spi.InjectionPoint;
@@ -55,7 +56,7 @@
public void injectMembers(T instance) {
Errors errors = new Errors(typeLiteral);
try {
- injectAndNotify(instance, errors);
+ injectAndNotify(instance, errors, false);
} catch (ErrorsException e) {
errors.merge(e.getErrors());
}
@@ -63,19 +64,29 @@
errors.throwProvisionExceptionIfErrorsExist();
}
- void injectAndNotify(final T instance, final Errors errors) throws
ErrorsException {
+ void injectAndNotify(final T instance, final Errors errors, final
boolean toolableOnly) throws ErrorsException {
if (instance == null) {
return;
}
injector.callInContext(new ContextualCallable<Void>() {
public Void call(InternalContext context) throws ErrorsException {
- injectMembers(instance, errors, context);
+ injectMembers(instance, errors, context, toolableOnly);
return null;
}
});
- notifyListeners(instance, errors);
+ // TODO: We *could* notify listeners too here,
+ // but it's not clear if we want to. There's no way to know
+ // if a MembersInjector from the usersMemberInjector list wants
+ // toolable injections, so do we really want to notify
+ // about injection? (We could take a strategy of only notifying
+ // if atleast one InjectionPoint was toolable, in which case
+ // the above callInContext could return 'true' if it injected
+ // anything.)
+ if(!toolableOnly) {
+ notifyListeners(instance, errors);
+ }
}
void notifyListeners(T instance, Errors errors) throws ErrorsException {
@@ -90,19 +101,25 @@
errors.throwIfNewErrors(numErrorsBefore);
}
- void injectMembers(T t, Errors errors, InternalContext context) {
+ void injectMembers(T t, Errors errors, InternalContext context, boolean
toolableOnly) {
// optimization: use manual for/each to save allocating an iterator
here
for (int i = 0, size = memberInjectors.size(); i < size; i++) {
- memberInjectors.get(i).inject(errors, context, t);
+ SingleMemberInjector injector = memberInjectors.get(i);
+ if(!toolableOnly || injector.getInjectionPoint().isToolable()) {
+ injector.inject(errors, context, t);
+ }
}
+ // TODO: There's no way to know if a user's MembersInjector wants
toolable injections.
+ if(!toolableOnly) {
// optimization: use manual for/each to save allocating an iterator
here
- for (int i = 0, size = userMembersInjectors.size(); i < size; i++) {
- MembersInjector<? super T> userMembersInjector =
userMembersInjectors.get(i);
- try {
- userMembersInjector.injectMembers(t);
- } catch (RuntimeException e) {
- errors.errorInUserInjector(userMembersInjector, typeLiteral, e);
+ for (int i = 0, size = userMembersInjectors.size(); i < size; i++) {
+ MembersInjector<? super T> userMembersInjector =
userMembersInjectors.get(i);
+ try {
+ userMembersInjector.injectMembers(t);
+ } catch (RuntimeException e) {
+ errors.errorInUserInjector(userMembersInjector, typeLiteral, e);
+ }
}
}
}
=======================================
--- /trunk/src/com/google/inject/spi/InjectionPoint.java Tue Sep 29
11:13:40 2009
+++ /trunk/src/com/google/inject/spi/InjectionPoint.java Fri Feb 5
13:12:05 2010
@@ -19,6 +19,7 @@
import com.google.inject.ConfigurationException;
import com.google.inject.Inject;
import com.google.inject.Key;
+import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.Errors;
@@ -155,6 +156,13 @@
public boolean isOptional() {
return optional;
}
+
+ /**
+ * Returns true if the element is annotated with {...@literal @}...@link
Toolable}.
+ */
+ public boolean isToolable() {
+ return ((AnnotatedElement)member).isAnnotationPresent(Toolable.class);
+ }
/**
* Returns the generic type that defines this injection point. If the
member exists on a
=======================================
--- /trunk/test/com/google/inject/AllTests.java Mon Oct 12 15:58:31 2009
+++ /trunk/test/com/google/inject/AllTests.java Fri Feb 5 13:12:05 2010
@@ -33,6 +33,7 @@
import com.google.inject.spi.ModuleRewriterTest;
import com.google.inject.spi.ProviderMethodsTest;
import com.google.inject.spi.SpiBindingsTest;
+import com.google.inject.spi.ToolStageInjectorTest;
import com.google.inject.util.NoopOverrideTest;
import com.google.inject.util.ProvidersTest;
import com.google.inject.util.TypesTest;
@@ -120,6 +121,7 @@
suite.addTestSuite(ModuleRewriterTest.class);
suite.addTestSuite(ProviderMethodsTest.class);
suite.addTestSuite(SpiBindingsTest.class);
+ suite.addTestSuite(ToolStageInjectorTest.class);
// tools
// suite.addTestSuite(JmxTest.class); not a testcase
=======================================
--- /trunk/test/com/google/inject/InjectorTest.java Tue Nov 11 14:41:56 2008
+++ /trunk/test/com/google/inject/InjectorTest.java Fri Feb 5 13:12:05 2010
@@ -289,39 +289,6 @@
}
});
}
-
- public void testToolStageInjectorRestrictions() {
- Injector injector = Guice.createInjector(Stage.TOOL);
- try {
- injector.injectMembers(new Object());
- fail("Non-SPI Injector methods must throw an exception in the TOOL
stage.");
- } catch (UnsupportedOperationException expected) {
- }
-
- try {
- injector.getInstance(Injector.class);
- fail("Non-SPI Injector methods must throw an exception in the TOOL
stage.");
- } catch (UnsupportedOperationException expected) {
- }
-
- try {
- injector.getInstance(Key.get(Injector.class));
- fail("Non-SPI Injector methods must throw an exception in the TOOL
stage.");
- } catch (UnsupportedOperationException expected) {
- }
-
- try {
- injector.getProvider(Injector.class);
- fail("Non-SPI Injector methods must throw an exception in the TOOL
stage.");
- } catch (UnsupportedOperationException expected) {
- }
-
- try {
- injector.getProvider(Key.get(Injector.class));
- fail("Non-SPI Injector methods must throw an exception in the TOOL
stage.");
- } catch (UnsupportedOperationException expected) {
- }
- }
public void testSubtypeNotProvided() {
try {
--
You received this message because you are subscribed to the Google Groups
"google-guice-dev" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/google-guice-dev?hl=en.