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.

Reply via email to