Add support for ProvisionListeners to notify on toInstance & constant bindings.
---------------------
Manually synced.
COMMIT=41634417
diff --git a/core/src/com/google/inject/internal/AbstractBindingProcessor.java b/core/src/com/google/inject/internal/AbstractBindingProcessor.java
index f09c40b..72dae2b 100644
--- a/core/src/com/google/inject/internal/AbstractBindingProcessor.java
+++ b/core/src/com/google/inject/internal/AbstractBindingProcessor.java
@@ -26,6 +26,7 @@
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Scope;
+import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.DefaultBindingTargetVisitor;
@@ -51,8 +52,8 @@
Module.class,
Provider.class,
Scope.class,
+ Stage.class,
TypeLiteral.class);
- // TODO(jessewilson): fix BuiltInModule, then add Stage
protected final ProcessedBindingData bindingData;
diff --git a/core/src/com/google/inject/internal/BindingProcessor.java b/core/src/com/google/inject/internal/BindingProcessor.java
index a87e326..7ce3ea0 100644
--- a/core/src/com/google/inject/internal/BindingProcessor.java
+++ b/core/src/com/google/inject/internal/BindingProcessor.java
@@ -87,8 +87,10 @@
prepareBinding();
Set<InjectionPoint> injectionPoints = binding.getInjectionPoints();
T instance = binding.getInstance();
+ @SuppressWarnings("unchecked") // safe to cast to binding<T> because
+ // the processor was constructed w/ it
Initializable<T> ref = initializer.requestInjection(
- injector, instance, key, source, injectionPoints);
+ injector, instance, (Binding<T>) binding, source, injectionPoints);
ConstantFactory<? extends T> factory = new ConstantFactory<T>(ref);
InternalFactory<? extends T> scopedFactory
= Scoping.scope(key, injector, factory, source, scoping);
diff --git a/core/src/com/google/inject/internal/Initializer.java b/core/src/com/google/inject/internal/Initializer.java
index 33dee8d..56c6873 100644
--- a/core/src/com/google/inject/internal/Initializer.java
+++ b/core/src/com/google/inject/internal/Initializer.java
@@ -21,6 +21,7 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
@@ -57,21 +58,25 @@
*
* @param instance an instance that optionally has members to be injected (each annotated with
* @Inject).
- * @param key a key to use for keeping the state of the dependency chain
+ * @param binding the binding that caused this initializable to be created, if it exists.
* @param source the source location that this injection was requested
*/
- <T> Initializable<T> requestInjection(InjectorImpl injector, T instance, Key<T> key,
+ <T> Initializable<T> requestInjection(InjectorImpl injector, T instance, Binding<T> binding,
Object source, Set<InjectionPoint> injectionPoints) {
checkNotNull(source);
- // short circuit if the object has no injections
- if (instance == null
- || (injectionPoints.isEmpty() && !injector.membersInjectorStore.hasTypeListeners())) {
+ ProvisionListenerStackCallback<T> provisionCallback =
+ binding == null ? null : injector.provisionListenerStore.get(binding);
+
+ // short circuit if the object has no injections or listeners.
+ if (instance == null || (injectionPoints.isEmpty()
+ && !injector.membersInjectorStore.hasTypeListeners()
+ && (provisionCallback == null || !provisionCallback.hasListeners()))) {
return Initializables.of(instance);
}
- InjectableReference<T> initializable =
- new InjectableReference<T>(injector, instance, key, source);
+ InjectableReference<T> initializable = new InjectableReference<T>(
+ injector, instance, binding == null ? null : binding.getKey(), provisionCallback, source);
pendingInjection.put(instance, initializable);
return initializable;
}
@@ -118,10 +123,13 @@
private final T instance;
private final Object source;
private final Key<T> key;
+ private final ProvisionListenerStackCallback<T> provisionCallback;
- public InjectableReference(InjectorImpl injector, T instance, Key<T> key, Object source) {
+ public InjectableReference(InjectorImpl injector, T instance, Key<T> key,
+ ProvisionListenerStackCallback<T> provisionCallback, Object source) {
this.injector = injector;
this.key = key; // possibly null!
+ this.provisionCallback = provisionCallback; // possibly null!
this.instance = checkNotNull(instance, "instance");
this.source = checkNotNull(source, "source");
}
@@ -163,7 +171,11 @@
// 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), key, source,
+ membersInjector.injectAndNotify(instance,
+ errors.withSource(source),
+ key,
+ provisionCallback,
+ source,
injector.options.stage == Stage.TOOL);
}
diff --git a/core/src/com/google/inject/internal/InjectorShell.java b/core/src/com/google/inject/internal/InjectorShell.java
index 4985f8c..043fdcf 100644
--- a/core/src/com/google/inject/internal/InjectorShell.java
+++ b/core/src/com/google/inject/internal/InjectorShell.java
@@ -129,9 +129,9 @@
checkState(privateElements == null || parent != null, "PrivateElements with no parent");
checkState(state != null, "no state. Did you remember to lock() ?");
- // bind Stage and Singleton if this is a top-level injector
+ // bind Singleton if this is a top-level injector
if (parent == null) {
- modules.add(0, new RootModule(stage));
+ modules.add(0, new RootModule());
}
elements.addAll(Elements.getElements(stage, modules));
@@ -174,6 +174,7 @@
new TypeConverterBindingProcessor(errors).process(injector, elements);
stopwatch.resetAndLog("Converters creation");
+ bindStage(injector, stage);
bindInjector(injector);
bindLogger(injector);
@@ -270,16 +271,21 @@
}
}
- private static class RootModule implements Module {
- final Stage stage;
-
- private RootModule(Stage stage) {
- this.stage = checkNotNull(stage, "stage");
+ private static void bindStage(InjectorImpl injector, Stage stage) {
+ Key<Stage> key = Key.get(Stage.class);
+ InstanceBindingImpl<Stage> stageBinding = new InstanceBindingImpl<Stage>(
+ injector,
+ key,
+ SourceProvider.UNKNOWN_SOURCE,
+ new ConstantFactory<Stage>(Initializables.of(stage)),
+ ImmutableSet.<InjectionPoint>of(),
+ stage);
+ injector.state.putBinding(key, stageBinding);
}
+ private static class RootModule implements Module {
public void configure(Binder binder) {
binder = binder.withSource(SourceProvider.UNKNOWN_SOURCE);
- binder.bind(Stage.class).toInstance(stage);
binder.bindScope(Singleton.class, SINGLETON);
binder.bindScope(javax.inject.Singleton.class, SINGLETON);
}
diff --git a/core/src/com/google/inject/internal/MembersInjectorImpl.java b/core/src/com/google/inject/internal/MembersInjectorImpl.java
index d405de5..efb8c52 100644
--- a/core/src/com/google/inject/internal/MembersInjectorImpl.java
+++ b/core/src/com/google/inject/internal/MembersInjectorImpl.java
@@ -21,6 +21,7 @@
import com.google.inject.Key;
import com.google.inject.MembersInjector;
import com.google.inject.TypeLiteral;
+import com.google.inject.internal.ProvisionListenerStackCallback.ProvisionCallback;
import com.google.inject.spi.InjectionListener;
import com.google.inject.spi.InjectionPoint;
@@ -58,7 +59,7 @@
public void injectMembers(T instance) {
Errors errors = new Errors(typeLiteral);
try {
- injectAndNotify(instance, errors, null, typeLiteral, false);
+ injectAndNotify(instance, errors, null, null, typeLiteral, false);
} catch (ErrorsException e) {
errors.merge(e.getErrors());
}
@@ -66,18 +67,31 @@
errors.throwProvisionExceptionIfErrorsExist();
}
- void injectAndNotify(final T instance, final Errors errors,
- final Key<T> key, final Object source, final boolean toolableOnly)
- throws ErrorsException {
+ void injectAndNotify(final T instance,
+ final Errors errors,
+ final Key<T> key, // possibly null!
+ final ProvisionListenerStackCallback<T> provisionCallback, // possibly null!
+ final Object source,
+ final boolean toolableOnly) throws ErrorsException {
if (instance == null) {
return;
}
injector.callInContext(new ContextualCallable<Void>() {
- public Void call(InternalContext context) throws ErrorsException {
+ @Override
+ public Void call(final InternalContext context) throws ErrorsException {
context.pushState(key, source);
try {
+ if (provisionCallback != null && provisionCallback.hasListeners()) {
+ provisionCallback.provision(errors, context, new ProvisionCallback<T>() {
+ @Override public T call() {
injectMembers(instance, errors, context, toolableOnly);
+ return instance;
+ }
+ });
+ } else {
+ injectMembers(instance, errors, context, toolableOnly);
+ }
} finally {
context.popState();
}
diff --git a/core/src/com/google/inject/internal/ProvisionListenerCallbackStore.java b/core/src/com/google/inject/internal/ProvisionListenerCallbackStore.java
index 8b2ed60..631a05f 100644
--- a/core/src/com/google/inject/internal/ProvisionListenerCallbackStore.java
+++ b/core/src/com/google/inject/internal/ProvisionListenerCallbackStore.java
@@ -18,15 +18,20 @@
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.inject.Binding;
+import com.google.inject.Injector;
import com.google.inject.Key;
+import com.google.inject.Stage;
import com.google.inject.spi.ProvisionListener;
import com.google.inject.spi.ProvisionListenerBinding;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.logging.Logger;
/**
* {@link ProvisionListenerStackCallback} for each key.
@@ -34,6 +39,12 @@
* @author sameb@google.com (Sam Berlin)
*/
final class ProvisionListenerCallbackStore {
+
+ // TODO(sameb): Consider exposing this in the API somehow? Maybe?
+ // Lots of code often want to skip over the internal stuffs.
+ private static final Set<Key<?>> INTERNAL_BINDINGS =
+ ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(Logger.class));
+
private final ImmutableList<ProvisionListenerBinding> listenerBindings;
private final Map<KeyBinding, ProvisionListenerStackCallback<?>> cache
@@ -52,7 +63,12 @@
*/
@SuppressWarnings("unchecked") // the ProvisionListenerStackCallback type always agrees with the passed type
public <T> ProvisionListenerStackCallback<T> get(Binding<T> binding) {
- return (ProvisionListenerStackCallback<T>) cache.get(new KeyBinding(binding.getKey(), binding));
+ // Never notify any listeners for internal bindings.
+ if (!INTERNAL_BINDINGS.contains(binding.getKey())) {
+ return (ProvisionListenerStackCallback<T>) cache.get(
+ new KeyBinding(binding.getKey(), binding));
+ }
+ return ProvisionListenerStackCallback.emptyListener();
}
/**
@@ -82,8 +98,10 @@
listeners.addAll(provisionBinding.getListeners());
}
}
- if (listeners == null) {
- listeners = ImmutableList.of();
+ if (listeners == null || listeners.isEmpty()) {
+ // Optimization: don't bother constructing the callback if there are
+ // no listeners.
+ return ProvisionListenerStackCallback.emptyListener();
}
return new ProvisionListenerStackCallback<T>(binding, listeners);
}
diff --git a/core/src/com/google/inject/internal/ProvisionListenerStackCallback.java b/core/src/com/google/inject/internal/ProvisionListenerStackCallback.java
index 4870ca6..bfcf931 100644
--- a/core/src/com/google/inject/internal/ProvisionListenerStackCallback.java
+++ b/core/src/com/google/inject/internal/ProvisionListenerStackCallback.java
@@ -16,6 +16,7 @@
package com.google.inject.internal;
+import com.google.common.collect.ImmutableList;
import com.google.inject.Binding;
import com.google.inject.ProvisionException;
import com.google.inject.spi.DependencyAndSource;
@@ -31,9 +32,18 @@
final class ProvisionListenerStackCallback<T> {
private static final ProvisionListener EMPTY_LISTENER[] = new ProvisionListener[0];
+ @SuppressWarnings("rawtypes")
+ private static final ProvisionListenerStackCallback<?> EMPTY_CALLBACK =
+ new ProvisionListenerStackCallback(null /* unused, so ok */, ImmutableList.of());
+
private final ProvisionListener[] listeners;
private final Binding<T> binding;
+ @SuppressWarnings("unchecked")
+ public static <T> ProvisionListenerStackCallback<T> emptyListener() {
+ return (ProvisionListenerStackCallback<T>) EMPTY_CALLBACK;
+ }
+
public ProvisionListenerStackCallback(Binding<T> binding, List<ProvisionListener> listeners) {
this.binding = binding;
if (listeners.isEmpty()) {
diff --git a/core/src/com/google/inject/spi/ProvisionListener.java b/core/src/com/google/inject/spi/ProvisionListener.java
index 4f523f6..e3f06bd 100644
--- a/core/src/com/google/inject/spi/ProvisionListener.java
+++ b/core/src/com/google/inject/spi/ProvisionListener.java
@@ -35,7 +35,9 @@
* Invoked by Guice when an object requires provisioning. Provisioning occurs
* when Guice locates and injects the dependencies for a binding. For types
* bound to a Provider, provisioning encapsulates the {@link Provider#get}
- * method. For other types, provisioning encapsulates the construction of the
+ * method. For toInstance or constant bindings, provisioning encapsulates
+ * the injecting of {@literal @}{@code Inject}ed fields or methods.
+ * For other types, provisioning encapsulates the construction of the
* object. If a type is bound within a {@link Scope}, provisioning depends on
* the scope. Types bound in Singleton scope will only be provisioned once.
* Types bound in no scope will be provisioned every time they are injected.
diff --git a/core/test/com/google/inject/BinderTest.java b/core/test/com/google/inject/BinderTest.java
index 193bbad..b52a153 100644
--- a/core/test/com/google/inject/BinderTest.java
+++ b/core/test/com/google/inject/BinderTest.java
@@ -456,6 +456,7 @@
bind(Module.class).annotatedWith(red).toProvider(Providers.<Module>of(null));
bind(Provider.class).annotatedWith(red).toProvider(Providers.<Provider>of(null));
bind(Scope.class).annotatedWith(red).toProvider(Providers.<Scope>of(null));
+ bind(Stage.class).annotatedWith(red).toProvider(Providers.<Stage>of(null));
bind(TypeLiteral.class).annotatedWith(red).toProvider(Providers.<TypeLiteral>of(null));
bind(new TypeLiteral<Key<String>>() {}).toProvider(Providers.<Key<String>>of(null));
}
@@ -471,6 +472,7 @@
"Binding to core guice framework type is not allowed: Module.",
"Binding to Provider is not allowed.",
"Binding to core guice framework type is not allowed: Scope.",
+ "Binding to core guice framework type is not allowed: Stage.",
"Binding to core guice framework type is not allowed: TypeLiteral.",
"Binding to core guice framework type is not allowed: Key.");
}
diff --git a/core/test/com/google/inject/BindingTest.java b/core/test/com/google/inject/BindingTest.java
index 46359d5..df91c44 100644
--- a/core/test/com/google/inject/BindingTest.java
+++ b/core/test/com/google/inject/BindingTest.java
@@ -370,8 +370,7 @@
}
});
- assertEquals(ImmutableSet.of(TypeLiteral.get(Stage.class), TypeLiteral.get(D.class)),
- heardTypes);
+ assertEquals(ImmutableSet.of(TypeLiteral.get(D.class)), heardTypes);
}
public void testInterfaceToImplementationConstructor() throws NoSuchMethodException {
diff --git a/core/test/com/google/inject/ProvisionListenerTest.java b/core/test/com/google/inject/ProvisionListenerTest.java
index 421cb89..860910a 100644
--- a/core/test/com/google/inject/ProvisionListenerTest.java
+++ b/core/test/com/google/inject/ProvisionListenerTest.java
@@ -21,17 +21,21 @@
import static com.google.inject.name.Names.named;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.matcher.Matcher;
import com.google.inject.matcher.Matchers;
import com.google.inject.name.Named;
import com.google.inject.spi.DependencyAndSource;
+import com.google.inject.spi.InstanceBinding;
import com.google.inject.spi.ProvisionListener;
+import com.google.inject.util.Providers;
import junit.framework.TestCase;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -244,6 +248,8 @@
throw new RuntimeException(ex);
}
bind(LinkedFoo.class).to(Foo.class);
+ bind(Interface.class).toInstance(new Implementation());
+ bindConstant().annotatedWith(named("constant")).to("MyConstant");
}
@Provides @Named("pi") Foo provideFooBar() {
@@ -251,6 +257,11 @@
}
});
+ // toInstance & constant bindings are notified in random order, at the very beginning.
+ assertEquals(
+ ImmutableSet.of(Key.get(Interface.class), Key.get(String.class, named("constant"))),
+ capturer.getAsSetAndClear());
+
// simple binding
assertNotNull(injector.getInstance(Foo.class));
assertEquals(of(Key.get(Foo.class)), capturer.getAndClear());
@@ -334,6 +345,9 @@
}
}
+ interface Interface {}
+ class Implementation implements Interface {}
+
@Singleton static class Sole {}
static class Many {}
@@ -370,8 +384,24 @@
public <T> void onProvision(ProvisionInvocation<T> provision) {
keys.add(provision.getBinding().getKey());
T provisioned = provision.provision();
+ // InstanceBindings are the only kind of binding where the key can
+ // be an instanceof the provisioned, because it isn't linked to any
+ // direct implementation. I guess maybe it'd also be possible
+ // with a toConstructor binding... but we don't use that in our tests.
+ if (provision.getBinding() instanceof InstanceBinding) {
+ Class<? super T> expected = provision.getBinding().getKey().getRawType();
+ assertTrue("expected instanceof: " + expected + ", but was: " + provisioned,
+ expected.isInstance(provisioned));
+ } else {
assertEquals(provision.getBinding().getKey().getRawType(), provisioned.getClass());
}
+ }
+
+ Set<Key> getAsSetAndClear() {
+ Set<Key> copy = ImmutableSet.copyOf(keys);
+ keys.clear();
+ return copy;
+ }
List<Key> getAndClear() {
List<Key> copy = ImmutableList.copyOf(keys);
@@ -454,8 +484,10 @@
// Build up a list of asserters for our dependency chains.
ImmutableList.Builder<Class<?>> chain = ImmutableList.builder();
+ chain.add(Instance.class);
+ bindListener(keyMatcher(Instance.class), new ChainAsserter(pList, chain.build()));
- chain.add(Instance.class).add(A.class);
+ chain.add(A.class);
bindListener(keyMatcher(A.class), new ChainAsserter(pList, chain.build()));
chain.add(B.class).add(BImpl.class);
@@ -482,7 +514,8 @@
});
Instance instance = injector.getInstance(Instance.class);
// make sure we're checking all of the chain asserters..
- assertEquals(of(A.class, BImpl.class, C.class, DP.class, D.class, E.class, F.class),
+ assertEquals(
+ of(Instance.class, A.class, BImpl.class, C.class, DP.class, D.class, E.class, F.class),
pList);
// and make sure that nothing else was notified that we didn't expect.
assertEquals(totalList, pList);
@@ -585,5 +618,22 @@
@SuppressWarnings("unused")
@Inject F f;
}
- private static class F {}
+ private static class F {
+ }
+
+ public void testBindToInjectorWithListeningGivesSaneException() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bindListener(Matchers.any(), new Counter());
+ bind(Injector.class).toProvider(Providers.<Injector>of(null));
+ }
+ });
+ fail();
+ } catch (CreationException ce) {
+ assertContains(
+ ce.getMessage(), "Binding to core guice framework type is not allowed: Injector.");
+ }
+ }
}
diff --git a/core/test/com/google/inject/TypeListenerTest.java b/core/test/com/google/inject/TypeListenerTest.java
index 5526d2e..549bb91 100644
--- a/core/test/com/google/inject/TypeListenerTest.java
+++ b/core/test/com/google/inject/TypeListenerTest.java
@@ -596,7 +596,8 @@
try {
Guice.createInjector(new AbstractModule() {
protected void configure() {
- bindListener(Matchers.only(new TypeLiteral<Stage>() {}), new TypeListener() {
+ requestInjection(new Object());
+ bindListener(Matchers.any(), new TypeListener() {
public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
encounter.addError("There was an error on %s", type);
encounter.addError(new IllegalArgumentException("whoops!"));
@@ -609,7 +610,7 @@
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(),
- "1) There was an error on com.google.inject.Stage",
+ "1) There was an error on java.lang.Object",
"2) An exception was caught and reported. Message: whoops!",
"3) And another problem",
"4) An exception was caught and reported. Message: null",
diff --git a/extensions/multibindings/test/com/google/inject/multibindings/SpiUtils.java b/extensions/multibindings/test/com/google/inject/multibindings/SpiUtils.java
index 11738e6..eb43eaf 100644
--- a/extensions/multibindings/test/com/google/inject/multibindings/SpiUtils.java
+++ b/extensions/multibindings/test/com/google/inject/multibindings/SpiUtils.java
@@ -500,7 +500,7 @@
return new MapResult<K, V>(k, new BindResult<V>(PROVIDER_INSTANCE, v, null));
}
- private static class MapResult<K, V> {
+ static class MapResult<K, V> {
private final K k;
private final BindResult<V> v;
@@ -534,7 +534,7 @@
/** The kind of binding. */
static enum BindType { INSTANCE, LINKED, PROVIDER_INSTANCE }
/** The result of the binding. */
- private static class BindResult<T> {
+ static class BindResult<T> {
private final BindType type;
private final Key<? extends T> key;
private final T instance;