Created
July 7, 2014 17:53
-
-
Save gissuebot/a9759687faed5e504537 to your computer and use it in GitHub Desktop.
Migrated attachment for Guice issue 38, comment 23
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### Eclipse Workspace Patch 1.0 | |
#P guice2 | |
Index: src/com/google/inject/internal/InjectorShell.java | |
=================================================================== | |
--- src/com/google/inject/internal/InjectorShell.java (revision 1004) | |
+++ src/com/google/inject/internal/InjectorShell.java (working copy) | |
@@ -139,6 +139,9 @@ | |
List<TypeListenerBinding> listenerBindings = injector.state.getTypeListenerBindings(); | |
injector.membersInjectorStore = new MembersInjectorStore(injector, listenerBindings); | |
stopwatch.resetAndLog("TypeListeners creation"); | |
+ | |
+ new BindingListenerBindingProcessor(errors).process(injector, elements); | |
+ stopwatch.resetAndLog("BindingListener creation"); | |
new ScopeBindingProcessor(errors).process(injector, elements); | |
stopwatch.resetAndLog("Scopes creation"); | |
Index: test/com/google/inject/spi/SpiBindingsTest.java | |
=================================================================== | |
--- test/com/google/inject/spi/SpiBindingsTest.java (revision 1001) | |
+++ test/com/google/inject/spi/SpiBindingsTest.java (working copy) | |
@@ -33,6 +33,7 @@ | |
import com.google.inject.internal.Lists; | |
import com.google.inject.name.Names; | |
import java.lang.reflect.Constructor; | |
+import java.util.Collection; | |
import java.util.Collections; | |
import java.util.Comparator; | |
import java.util.Iterator; | |
@@ -380,4 +381,71 @@ | |
private static class D extends C { | |
@Inject public D(Injector unused) { } | |
} | |
+ | |
+ private static interface JIT_A {} | |
+ private static class JIT_B implements JIT_A {} | |
+ private static class JIT_C {} | |
+ @Singleton private static class JIT_D {} | |
+ | |
+ public void testGetJitBindings() { | |
+ Injector injector = Guice.createInjector(new AbstractModule() { | |
+ protected void configure() { | |
+ bind(JIT_A.class).to(JIT_B.class); | |
+ } | |
+ }); | |
+ | |
+ // assert implicit constructor binding is created when doing bind(..).to(..) | |
+ Collection<Binding<?>> jitBindings1 = injector.getJustInTimeBindings().values(); | |
+ assertEquals(1, jitBindings1.size()); | |
+ jitBindings1.iterator().next().acceptVisitor(new FailingElementVisitor() { | |
+ @Override public <T> Void visit(Binding<T> binding) { | |
+ assertEquals(Key.get(JIT_B.class), binding.getKey()); | |
+ binding.acceptScopingVisitor(new FailingBindingScopingVisitor() { | |
+ @Override public Void visitNoScoping() { | |
+ return null; | |
+ } | |
+ }); | |
+ return null; | |
+ } | |
+ }); | |
+ | |
+ // assert implicit binding is created when retrieving new provider, | |
+ // and that prior retrieved bindings do not modify. | |
+ injector.getProvider(JIT_C.class); | |
+ List<Binding<?>> jitBindings2 = Lists.newArrayList(injector.getJustInTimeBindings().values()); | |
+ assertEquals(1, jitBindings1.size()); // assert original list didn't change. | |
+ assertEquals(2, jitBindings2.size()); // new list contains it. | |
+ jitBindings2.removeAll(jitBindings1); | |
+ assertEquals(1, jitBindings1.size()); // after removing JIT_B, only 1 left. | |
+ jitBindings2.get(0).acceptVisitor(new FailingElementVisitor() { | |
+ @Override public <T> Void visit(Binding<T> binding) { | |
+ assertEquals(Key.get(JIT_C.class), binding.getKey()); | |
+ binding.acceptScopingVisitor(new FailingBindingScopingVisitor() { | |
+ @Override public Void visitNoScoping() { | |
+ return null; | |
+ } | |
+ }); | |
+ return null; | |
+ } | |
+ }); | |
+ | |
+ // assert scope is set properly on JIT bindings. | |
+ injector.getProvider(JIT_D.class); | |
+ List<Binding<?>> jitBindings3 = Lists.newArrayList(injector.getJustInTimeBindings().values()); | |
+ jitBindings3.removeAll(jitBindings2); | |
+ jitBindings3.removeAll(jitBindings1); | |
+ assertEquals(1, jitBindings3.size()); | |
+ jitBindings3.get(0).acceptVisitor(new FailingElementVisitor() { | |
+ @Override public <T> Void visit(Binding<T> binding) { | |
+ assertEquals(Key.get(JIT_D.class), binding.getKey()); | |
+ binding.acceptScopingVisitor(new FailingBindingScopingVisitor() { | |
+ @Override public Void visitScope(Scope scope) { | |
+ assertSame(Scopes.SINGLETON, scope); | |
+ return null; | |
+ } | |
+ }); | |
+ return null; | |
+ } | |
+ }); | |
+ } | |
} | |
Index: src/com/google/inject/spi/ElementVisitor.java | |
=================================================================== | |
--- src/com/google/inject/spi/ElementVisitor.java (revision 1004) | |
+++ src/com/google/inject/spi/ElementVisitor.java (working copy) | |
@@ -86,4 +86,9 @@ | |
* Visit an injectable type listener binding. | |
*/ | |
V visit(TypeListenerBinding binding); | |
+ | |
+ /** | |
+ * Visit a binding listener binding. | |
+ */ | |
+ V visit(BindingListenerBinding binding); | |
} | |
Index: src/com/google/inject/spi/BindingListenerBinding.java | |
=================================================================== | |
--- src/com/google/inject/spi/BindingListenerBinding.java (revision 0) | |
+++ src/com/google/inject/spi/BindingListenerBinding.java (revision 0) | |
@@ -0,0 +1,64 @@ | |
+/** | |
+ * Copyright (C) 2009 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.spi; | |
+ | |
+import com.google.inject.Binder; | |
+import com.google.inject.Binding; | |
+import com.google.inject.matcher.Matcher; | |
+ | |
+/** | |
+ * Binds types (picked using a Matcher) to a binding. Registrations are created explicitly in | |
+ * a module using {@link com.google.inject.Binder#bindBindingListener(Matcher, BindingListener)} statements. | |
+ * | |
+ * @author sberlin@gmail.com (Sam Berlin) | |
+ * @since 2.1 | |
+ */ | |
+public final class BindingListenerBinding implements Element { | |
+ | |
+ private final Object source; | |
+ private final Matcher<? super Binding<?>> bindingMatcher; | |
+ private final BindingListener listener; | |
+ | |
+ BindingListenerBinding(Object source, BindingListener listener, | |
+ Matcher<? super Binding<?>> bindingMatcher) { | |
+ this.source = source; | |
+ this.listener = listener; | |
+ this.bindingMatcher = bindingMatcher; | |
+ } | |
+ | |
+ /** Returns the registered listener. */ | |
+ public BindingListener getListener() { | |
+ return listener; | |
+ } | |
+ | |
+ /** Returns the binding matcher which chooses which binding the listener should be notified of. */ | |
+ public Matcher<? super Binding<?>> getBindingMatcher() { | |
+ return bindingMatcher; | |
+ } | |
+ | |
+ public Object getSource() { | |
+ return source; | |
+ } | |
+ | |
+ public <T> T acceptVisitor(ElementVisitor<T> visitor) { | |
+ return visitor.visit(this); | |
+ } | |
+ | |
+ public void applyTo(Binder binder) { | |
+ binder.withSource(getSource()).bindBindingListener(bindingMatcher, listener); | |
+ } | |
+} | |
Index: src/com/google/inject/spi/Elements.java | |
=================================================================== | |
--- src/com/google/inject/spi/Elements.java (revision 1004) | |
+++ src/com/google/inject/spi/Elements.java (working copy) | |
@@ -205,6 +205,12 @@ | |
public void bindListener(Matcher<? super TypeLiteral<?>> typeMatcher, TypeListener listener) { | |
elements.add(new TypeListenerBinding(getSource(), listener, typeMatcher)); | |
} | |
+ | |
+ @Override | |
+ public void bindBindingListener(Matcher<? super Binding<?>> bindingMatcher, | |
+ BindingListener listener) { | |
+ elements.add(new BindingListenerBinding(getSource(), listener, bindingMatcher)); | |
+ } | |
public void requestStaticInjection(Class<?>... types) { | |
for (Class<?> type : types) { | |
Index: src/com/google/inject/spi/DefaultElementVisitor.java | |
=================================================================== | |
--- src/com/google/inject/spi/DefaultElementVisitor.java (revision 1004) | |
+++ src/com/google/inject/spi/DefaultElementVisitor.java (working copy) | |
@@ -82,4 +82,8 @@ | |
public V visit(TypeListenerBinding binding) { | |
return visitOther(binding); | |
} | |
+ | |
+ public V visit(BindingListenerBinding binding) { | |
+ return visitOther(binding); | |
+ } | |
} | |
Index: src/com/google/inject/Binder.java | |
=================================================================== | |
--- src/com/google/inject/Binder.java (revision 1001) | |
+++ src/com/google/inject/Binder.java (working copy) | |
@@ -20,6 +20,7 @@ | |
import com.google.inject.binder.AnnotatedConstantBindingBuilder; | |
import com.google.inject.binder.LinkedBindingBuilder; | |
import com.google.inject.matcher.Matcher; | |
+import com.google.inject.spi.BindingListener; | |
import com.google.inject.spi.Message; | |
import com.google.inject.spi.TypeConverter; | |
import com.google.inject.spi.TypeListener; | |
@@ -360,6 +361,16 @@ | |
*/ | |
void bindListener(Matcher<? super TypeLiteral<?>> typeMatcher, | |
TypeListener listener); | |
+ | |
+ /** | |
+ * Registers a listener for bindings. Guice will notify the listener when it encounters a | |
+ * new binding and the binding is ready for use. | |
+ * | |
+ * @param bindMatcher matches bindings the listener should be notified of | |
+ * @param listener the listener that will be notified of bindings that match the bindMatcher | |
+ * @since 2.1 | |
+ */ | |
+ void bindBindingListener(Matcher<? super Binding<?>> bindingMatcher, BindingListener listener); | |
/** | |
* Returns a binder that uses {@code source} as the reference location for | |
Index: src/com/google/inject/internal/InjectorBuilder.java | |
=================================================================== | |
--- src/com/google/inject/internal/InjectorBuilder.java (revision 1004) | |
+++ src/com/google/inject/internal/InjectorBuilder.java (working copy) | |
@@ -24,6 +24,7 @@ | |
import com.google.inject.Provider; | |
import com.google.inject.Stage; | |
import com.google.inject.TypeLiteral; | |
+import com.google.inject.spi.BindingListenerBinding; | |
import com.google.inject.spi.Dependency; | |
import java.util.Collection; | |
import java.util.List; | |
@@ -172,6 +173,16 @@ | |
initializer.injectAll(errors); | |
stopwatch.resetAndLog("Instance injection"); | |
errors.throwCreationExceptionIfErrorsExist(); | |
+ | |
+ // we set ready to true before notifying listeners | |
+ // & before creating eager singletons so that any JIT | |
+ // bindings discovered in the injector while notifying | |
+ // or creating the singletons are also notified. | |
+ for (InjectorShell shell : shells) { | |
+ shell.getInjector().ready = true; | |
+ notifyBindingListeners(shell.getInjector(), stage, errors); | |
+ } | |
+ stopwatch.resetAndLog("Notifying binding listeners"); | |
for (InjectorShell shell : shells) { | |
loadEagerSingletons(shell.getInjector(), stage, errors); | |
@@ -179,6 +190,23 @@ | |
stopwatch.resetAndLog("Preloading singletons"); | |
errors.throwCreationExceptionIfErrorsExist(); | |
} | |
+ | |
+ private void notifyBindingListeners(InjectorImpl injector, Stage stage, final Errors errors) { | |
+ List<BindingListenerBinding> listenerBindings = injector.state.getBindingListenerBindings(); | |
+ // optimization: exit early without iterating over bindings if | |
+ // there are no listeners (the common case). | |
+ if(listenerBindings.isEmpty()) { | |
+ return; | |
+ } | |
+ | |
+ @SuppressWarnings("unchecked") // casting Collection<Binding> to Collection<BindingImpl> is safe | |
+ Set<BindingImpl<?>> candidateBindings = ImmutableSet.copyOf(Iterables.concat( | |
+ (Collection) injector.state.getExplicitBindingsThisLevel().values(), | |
+ injector.jitBindings.values())); | |
+ for(Binding<?> binding : candidateBindings) { | |
+ injector.notifyBindingListeners(listenerBindings, binding, errors); | |
+ } | |
+ } | |
/** | |
* Loads eager singletons, or all singletons if we're in Stage.PRODUCTION. Bindings discovered | |
@@ -245,6 +273,9 @@ | |
public Map<Key<?>, Binding<?>> getBindings() { | |
return this.delegateInjector.getBindings(); | |
} | |
+ public Map<Key<?>, Binding<?>> getJustInTimeBindings() { | |
+ return this.delegateInjector.getJustInTimeBindings(); | |
+ } | |
public <T> Binding<T> getBinding(Key<T> key) { | |
return this.delegateInjector.getBinding(key); | |
} | |
Index: src/com/google/inject/AbstractModule.java | |
=================================================================== | |
--- src/com/google/inject/AbstractModule.java (revision 1001) | |
+++ src/com/google/inject/AbstractModule.java (working copy) | |
@@ -22,6 +22,7 @@ | |
import static com.google.inject.internal.Preconditions.checkNotNull; | |
import static com.google.inject.internal.Preconditions.checkState; | |
import com.google.inject.matcher.Matcher; | |
+import com.google.inject.spi.BindingListener; | |
import com.google.inject.spi.Message; | |
import com.google.inject.spi.TypeConverter; | |
import com.google.inject.spi.TypeListener; | |
@@ -250,4 +251,14 @@ | |
TypeListener listener) { | |
binder.bindListener(typeMatcher, listener); | |
} | |
+ | |
+ /** | |
+ * @see Binder#bindBindingListener(com.google.inject.matcher.Matcher, | |
+ * com.google.inject.spi.BindingListener) | |
+ * @since 2.1 | |
+ */ | |
+ protected void bindBindingListener(Matcher<? super Binding<?>> bindingMatcher, | |
+ BindingListener listener) { | |
+ binder.bindBindingListener(bindingMatcher, listener); | |
+ } | |
} | |
Index: src/com/google/inject/internal/AbstractProcessor.java | |
=================================================================== | |
--- src/com/google/inject/internal/AbstractProcessor.java (revision 1004) | |
+++ src/com/google/inject/internal/AbstractProcessor.java (working copy) | |
@@ -17,6 +17,7 @@ | |
package com.google.inject.internal; | |
import com.google.inject.Binding; | |
+import com.google.inject.spi.BindingListenerBinding; | |
import com.google.inject.spi.Element; | |
import com.google.inject.spi.ElementVisitor; | |
import com.google.inject.spi.InjectionRequest; | |
@@ -119,4 +120,8 @@ | |
public Boolean visit(TypeListenerBinding binding) { | |
return false; | |
} | |
+ | |
+ public Boolean visit(BindingListenerBinding binding) { | |
+ return false; | |
+ } | |
} | |
Index: src/com/google/inject/internal/InjectorImpl.java | |
=================================================================== | |
--- src/com/google/inject/internal/InjectorImpl.java (revision 1004) | |
+++ src/com/google/inject/internal/InjectorImpl.java (working copy) | |
@@ -29,6 +29,8 @@ | |
import com.google.inject.ProvisionException; | |
import com.google.inject.TypeLiteral; | |
import static com.google.inject.internal.Annotations.findScopeAnnotation; | |
+ | |
+import com.google.inject.spi.BindingListenerBinding; | |
import com.google.inject.spi.BindingTargetVisitor; | |
import com.google.inject.spi.ConvertedConstantBinding; | |
import com.google.inject.spi.Dependency; | |
@@ -61,6 +63,10 @@ | |
/** Just-in-time binding cache. Guarded by state.lock() */ | |
final Map<Key<?>, BindingImpl<?>> jitBindings = Maps.newHashMap(); | |
+ | |
+ // true if the injector is ready to notify listeners of JIT bindings | |
+ // TODO: volatile? is this state already stored elsewhere? | |
+ boolean ready = false; | |
Lookups lookups = new DeferredLookups(this); | |
@@ -574,8 +580,23 @@ | |
BindingImpl<T> binding = createJustInTimeBinding(key, errors); | |
state.parent().blacklist(key); | |
jitBindings.put(key, binding); | |
+ if(ready) { | |
+ notifyBindingListeners(state.getBindingListenerBindings(), binding, errors); | |
+ } | |
return binding; | |
} | |
+ | |
+ void notifyBindingListeners(List<BindingListenerBinding> listeners, Binding<?> binding, Errors errors) { | |
+ for(BindingListenerBinding listener : listeners) { | |
+ if(listener.getBindingMatcher().matches(binding)) { | |
+ try { | |
+ listener.getListener().hear(binding); | |
+ } catch(RuntimeException re) { | |
+ errors.errorNotifyingBindingListener(listener, binding, re); | |
+ } | |
+ } | |
+ } | |
+ } | |
/** | |
* Returns a new just-in-time binding created by resolving {@code key}. The strategies used to | |
@@ -652,6 +673,12 @@ | |
public Map<Key<?>, Binding<?>> getBindings() { | |
return state.getExplicitBindingsThisLevel(); | |
} | |
+ | |
+ public Map<Key<?>, Binding<?>> getJustInTimeBindings() { | |
+ synchronized(state.lock()) { | |
+ return new ImmutableMap.Builder<Key<?>, Binding<?>>().putAll(jitBindings).build(); | |
+ } | |
+ } | |
private static class BindingsMultimap { | |
final Map<TypeLiteral<?>, List<Binding<?>>> multimap = Maps.newHashMap(); | |
Index: src/com/google/inject/internal/State.java | |
=================================================================== | |
--- src/com/google/inject/internal/State.java (revision 1004) | |
+++ src/com/google/inject/internal/State.java (working copy) | |
@@ -20,6 +20,7 @@ | |
import com.google.inject.Key; | |
import com.google.inject.Scope; | |
import com.google.inject.TypeLiteral; | |
+import com.google.inject.spi.BindingListenerBinding; | |
import com.google.inject.spi.TypeListenerBinding; | |
import java.lang.annotation.Annotation; | |
import java.util.List; | |
@@ -84,11 +85,19 @@ | |
public void addTypeListener(TypeListenerBinding typeListenerBinding) { | |
throw new UnsupportedOperationException(); | |
} | |
- | |
+ | |
public List<TypeListenerBinding> getTypeListenerBindings() { | |
return ImmutableList.of(); | |
} | |
+ public void addBindingListener(BindingListenerBinding bindingListenerBinding) { | |
+ throw new UnsupportedOperationException(); | |
+ } | |
+ | |
+ public List<BindingListenerBinding> getBindingListenerBindings() { | |
+ return ImmutableList.of(); | |
+ } | |
+ | |
public void blacklist(Key<?> key) { | |
} | |
@@ -133,8 +142,13 @@ | |
void addTypeListener(TypeListenerBinding typeListenerBinding); | |
+ | |
List<TypeListenerBinding> getTypeListenerBindings(); | |
+ void addBindingListener(BindingListenerBinding binding); | |
+ | |
+ List<BindingListenerBinding> getBindingListenerBindings(); | |
+ | |
/** | |
* Forbids the corresponding injector from creating a binding to {@code key}. Child injectors | |
* blacklist their bound keys on their parent injectors to prevent just-in-time bindings on the | |
@@ -153,4 +167,5 @@ | |
* to be used when reading mutable data (ie. just-in-time bindings, and binding blacklists). | |
*/ | |
Object lock(); | |
+ | |
} | |
Index: test/com/google/inject/BindingListenerTest.java | |
=================================================================== | |
--- test/com/google/inject/BindingListenerTest.java (revision 0) | |
+++ test/com/google/inject/BindingListenerTest.java (revision 0) | |
@@ -0,0 +1,202 @@ | |
+/** | |
+ * Copyright (C) 2009 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; | |
+ | |
+import static com.google.inject.Asserts.assertContains; | |
+ | |
+import java.util.concurrent.atomic.AtomicInteger; | |
+ | |
+import junit.framework.TestCase; | |
+ | |
+import com.google.inject.matcher.AbstractMatcher; | |
+import com.google.inject.matcher.Matcher; | |
+import com.google.inject.spi.BindingListener; | |
+ | |
+/** | |
+ * @author sberlin@gmail.com (Sam Berlin) | |
+ */ | |
+public class BindingListenerTest extends TestCase { | |
+ | |
+ private final Matcher<Binding<?>> onlyAbcd = matcherFor(new TypeLiteral<A>() {}) | |
+ .or(matcherFor(new TypeLiteral<B>() {})) | |
+ .or(matcherFor(new TypeLiteral<C>() {})) | |
+ .or(matcherFor(new TypeLiteral<D>() {})); | |
+ | |
+ private static Matcher<Binding<?>> matcherFor(final TypeLiteral<?> type) { | |
+ return new AbstractMatcher<Binding<?>>() { | |
+ @Override | |
+ public boolean matches(Binding<?> binding) { | |
+ return binding.getKey().getTypeLiteral().equals(type); | |
+ } | |
+ }; | |
+ } | |
+ | |
+ final BindingListener failingBindingListener = new BindingListener() { | |
+ int failures = 0; | |
+ | |
+ public void hear(Binding<?> binding) { | |
+ throw new ClassCastException("whoops, failure #" + (++failures)); | |
+ } | |
+ | |
+ @Override public String toString() { | |
+ return "clumsy"; | |
+ } | |
+ }; | |
+ | |
+ public void testBindingListenersAreFired() throws NoSuchMethodException { | |
+ final AtomicInteger firedCount = new AtomicInteger(); | |
+ | |
+ final BindingListener bindingListener = new BindingListener() { | |
+ public void hear(Binding<?> binding) { | |
+ assertEquals(new TypeLiteral<A>() {}, binding.getKey().getTypeLiteral()); | |
+ firedCount.incrementAndGet(); | |
+ } | |
+ }; | |
+ | |
+ Guice.createInjector(new AbstractModule() { | |
+ protected void configure() { | |
+ bindBindingListener(onlyAbcd, bindingListener); | |
+ bind(A.class); | |
+ } | |
+ }); | |
+ | |
+ assertEquals(1, firedCount.get()); | |
+ } | |
+ | |
+ public void testBindListenerThrows() { | |
+ try { | |
+ Guice.createInjector(new AbstractModule() { | |
+ protected void configure() { | |
+ bindBindingListener(onlyAbcd, failingBindingListener); | |
+ bind(B.class); | |
+ bind(C.class); | |
+ } | |
+ }); | |
+ fail(); | |
+ } catch (CreationException expected) { | |
+ assertContains(expected.getMessage(), | |
+ "1) Error notifying BindingListener clumsy (bound at " + getClass().getName(), | |
+ ".configure(BindingListenerTest.java:", | |
+ "of ConstructorBinding[key=" + Key.get(B.class) + ", source=" + getClass().getName(), | |
+ ".configure(BindingListenerTest.java:", | |
+ "), scope=Scopes.NO_SCOPE].", | |
+ "Reason: java.lang.ClassCastException: whoops, failure #1", | |
+ "2) Error notifying BindingListener clumsy (bound at " + getClass().getName(), | |
+ ".configure(BindingListenerTest.java:", | |
+ "of ConstructorBinding[key=" + Key.get(C.class) + ", source=" + getClass().getName(), | |
+ ".configure(BindingListenerTest.java:", | |
+ "), scope=Scopes.NO_SCOPE].", | |
+ "Reason: java.lang.ClassCastException: whoops, failure #2"); | |
+ } | |
+ | |
+ Injector injector = Guice.createInjector(new AbstractModule() { | |
+ protected void configure() { | |
+ bindBindingListener(onlyAbcd, failingBindingListener); | |
+ } | |
+ }); | |
+ try { | |
+ injector.getProvider(B.class); | |
+ fail(); | |
+ } catch (ConfigurationException expected) { | |
+ assertContains(expected.getMessage(), | |
+ "1) Error notifying BindingListener clumsy (bound at " + getClass().getName(), | |
+ ".configure(BindingListenerTest.java:", | |
+ "of ConstructorBinding[key=" + Key.get(B.class) + ", source=class " + B.class.getName() + ", scope=Scopes.NO_SCOPE].", | |
+ "Reason: java.lang.ClassCastException: whoops, failure #3"); | |
+ } | |
+ | |
+ // TODO: What should retrieving it again do? | |
+ // As far as we're concerned, everything is fine -- | |
+ // It's only in the listener that something failed. | |
+ // The current code allows the binding to be retrieved | |
+ // on subsequent gets, but is that right? Should it act | |
+ // as if it discovered a brand new binding, or as if | |
+ // the old exception is rethrown and the binding doesn't exist? | |
+ // Testing current behavior: | |
+ injector.getInstance(B.class); | |
+ | |
+ | |
+ // This is what TypeListener does: | |
+// // getting it again should yield the same exception #3 | |
+// try { | |
+// injector.getInstance(B.class); | |
+// fail(); | |
+// } catch (ConfigurationException expected) { | |
+// assertContains(expected.getMessage(), | |
+// "1) Error notifying BindingListener clumsy (bound at " + getClass().getName(), | |
+// ".configure(BindingListenerTest.java:", | |
+// "of ConstructorBinding[key=" + Key.get(B.class) + ", source=" + getClass().getName(), | |
+// ".configure(BindingListenerTest.java:", | |
+// "), scope=Scopes.NO_SCOPE].", | |
+// "Reason: java.lang.ClassCastException: whoops, failure #3"); | |
+// } | |
+ | |
+ // non-injected types do not participate | |
+ assertSame(Stage.DEVELOPMENT, injector.getInstance(Stage.class)); | |
+ } | |
+ | |
+ public void testBindingsWithNoInjectableMembersAreNotified() { | |
+ final AtomicInteger notificationCount = new AtomicInteger(); | |
+ | |
+ Guice.createInjector(new AbstractModule() { | |
+ protected void configure() { | |
+ bindBindingListener(onlyAbcd, new BindingListener() { | |
+ public void hear(Binding<?> binding) { | |
+ notificationCount.incrementAndGet(); | |
+ } | |
+ }); | |
+ | |
+ bind(C.class).toInstance(new C()); | |
+ } | |
+ }); | |
+ | |
+ assertEquals(1, notificationCount.get()); | |
+ } | |
+ | |
+ static class A { | |
+ @Inject Injector injector; | |
+ @Inject Stage stage; | |
+ } | |
+ | |
+ static class B {} | |
+ | |
+ public static class C { | |
+ public String buzz() { | |
+ return "buzz"; | |
+ } | |
+ | |
+ public String beep() { | |
+ return "beep"; | |
+ } | |
+ } | |
+ | |
+ static class D { | |
+ int guiceInjected = 0; | |
+ int userInjected = 0; | |
+ int listenersNotified = 0; | |
+ | |
+ @Inject void guiceInjected() { | |
+ guiceInjected++; | |
+ } | |
+ | |
+ void assertAllCounts(int expected) { | |
+ assertEquals(expected, guiceInjected); | |
+ assertEquals(expected, userInjected); | |
+ assertEquals(expected, listenersNotified); | |
+ } | |
+ } | |
+} | |
Index: src/com/google/inject/internal/BindingListenerBindingProcessor.java | |
=================================================================== | |
--- src/com/google/inject/internal/BindingListenerBindingProcessor.java (revision 0) | |
+++ src/com/google/inject/internal/BindingListenerBindingProcessor.java (revision 0) | |
@@ -0,0 +1,37 @@ | |
+/** | |
+ * Copyright (C) 2009 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.internal; | |
+ | |
+import com.google.inject.Binder; | |
+import com.google.inject.spi.BindingListenerBinding; | |
+ | |
+/** | |
+ * Handles {@link Binder#bindBindingListener} commands. | |
+ * | |
+ * @author sberlin@gmail.com (Sam Berlin) | |
+ */ | |
+class BindingListenerBindingProcessor extends AbstractProcessor { | |
+ | |
+ BindingListenerBindingProcessor(Errors errors) { | |
+ super(errors); | |
+ } | |
+ | |
+ @Override public Boolean visit(BindingListenerBinding binding) { | |
+ injector.state.addBindingListener(binding); | |
+ return true; | |
+ } | |
+} | |
\ No newline at end of file | |
Index: test/com/google/inject/InjectorTest.java | |
=================================================================== | |
--- test/com/google/inject/InjectorTest.java (revision 1001) | |
+++ test/com/google/inject/InjectorTest.java (working copy) | |
@@ -27,6 +27,9 @@ | |
import java.util.concurrent.Executors; | |
import java.util.concurrent.Future; | |
import java.util.concurrent.atomic.AtomicReference; | |
+ | |
+import com.google.inject.internal.Iterables; | |
+ | |
import junit.framework.TestCase; | |
/** | |
@@ -122,9 +125,15 @@ | |
assertNotSerializable(injector); | |
assertNotSerializable(injector.getProvider(String.class)); | |
assertNotSerializable(injector.getBinding(String.class)); | |
- for (Binding<?> binding : injector.getBindings().values()) { | |
+ assertTrue(injector.getBindings().size() > 0); | |
+ assertTrue(injector.getJustInTimeBindings().size() > 0); | |
+ for (Binding<?> binding : | |
+ Iterables.concat(injector.getBindings().values(), | |
+ injector.getJustInTimeBindings().values())) { | |
assertNotSerializable(binding); | |
} | |
+ | |
+ | |
} | |
static class IntegerWrapper { | |
Index: src/com/google/inject/spi/BindingListener.java | |
=================================================================== | |
--- src/com/google/inject/spi/BindingListener.java (revision 0) | |
+++ src/com/google/inject/spi/BindingListener.java (revision 0) | |
@@ -0,0 +1,9 @@ | |
+package com.google.inject.spi; | |
+ | |
+import com.google.inject.Binding; | |
+ | |
+public interface BindingListener { | |
+ | |
+ void hear(Binding<?> newBinding); | |
+ | |
+} | |
Index: src/com/google/inject/Injector.java | |
=================================================================== | |
--- src/com/google/inject/Injector.java (revision 1001) | |
+++ src/com/google/inject/Injector.java (working copy) | |
@@ -100,6 +100,18 @@ | |
* <p>This method is part of the Guice SPI and is intended for use by tools and extensions. | |
*/ | |
Map<Key<?>, Binding<?>> getBindings(); | |
+ | |
+ /** | |
+ * Returns all just-in-time bindings. Just-in-time bindings are also referred to as | |
+ * implicit or synthetic bindings. These are bindings that the user did not bind explicitly, | |
+ * but Guice implicitly creates in order to satisfy an injection. | |
+ * | |
+ * <p>The returned map does not include bindings inherited from a {@link #getParent() parent | |
+ * injector}, should one exist. | |
+ * | |
+ * <p>This method is part of the Guice SPI and is intended for use by tools and extensions. | |
+ */ | |
+ Map<Key<?>, Binding<?>> getJustInTimeBindings(); | |
/** | |
* Returns the binding for the given injection key. This will be an explicit bindings if the key | |
Index: src/com/google/inject/internal/Errors.java | |
=================================================================== | |
--- src/com/google/inject/internal/Errors.java (revision 1004) | |
+++ src/com/google/inject/internal/Errors.java (working copy) | |
@@ -16,6 +16,7 @@ | |
package com.google.inject.internal; | |
+import com.google.inject.Binding; | |
import com.google.inject.ConfigurationException; | |
import com.google.inject.CreationException; | |
import com.google.inject.Key; | |
@@ -24,6 +25,7 @@ | |
import com.google.inject.ProvisionException; | |
import com.google.inject.Scope; | |
import com.google.inject.TypeLiteral; | |
+import com.google.inject.spi.BindingListenerBinding; | |
import com.google.inject.spi.Dependency; | |
import com.google.inject.spi.InjectionListener; | |
import com.google.inject.spi.InjectionPoint; | |
@@ -277,6 +279,14 @@ | |
+ " Reason: %s", | |
listener.getListener(), convert(listener.getSource()), type, cause); | |
} | |
+ | |
+ public Errors errorNotifyingBindingListener(BindingListenerBinding listener, | |
+ Binding<?> binding, Throwable cause) { | |
+ return errorInUserCode(cause, | |
+ "Error notifying BindingListener %s (bound at %s) of %s.\n" | |
+ + " Reason: %s", | |
+ listener.getListener(), convert(listener.getSource()), binding, cause); | |
+ } | |
public Errors errorInjectingConstructor(Throwable cause) { | |
return errorInUserCode(cause, "Error injecting constructor, %s", cause); | |
Index: src/com/google/inject/internal/InheritingState.java | |
=================================================================== | |
--- src/com/google/inject/internal/InheritingState.java (revision 1004) | |
+++ src/com/google/inject/internal/InheritingState.java (working copy) | |
@@ -21,6 +21,8 @@ | |
import com.google.inject.Scope; | |
import com.google.inject.TypeLiteral; | |
import static com.google.inject.internal.Preconditions.checkNotNull; | |
+ | |
+import com.google.inject.spi.BindingListenerBinding; | |
import com.google.inject.spi.TypeListenerBinding; | |
import java.lang.annotation.Annotation; | |
import java.util.ArrayList; | |
@@ -45,6 +47,7 @@ | |
private final List<MethodAspect> methodAspects = Lists.newArrayList(); | |
/*end[AOP]*/ | |
private final List<TypeListenerBinding> listenerBindings = Lists.newArrayList(); | |
+ private final List<BindingListenerBinding> bindListenerBindings = Lists.newArrayList(); | |
private final WeakKeySet blacklistedKeys = new WeakKeySet(); | |
private final Object lock; | |
@@ -120,6 +123,7 @@ | |
public void addTypeListener(TypeListenerBinding listenerBinding) { | |
listenerBindings.add(listenerBinding); | |
} | |
+ | |
public List<TypeListenerBinding> getTypeListenerBindings() { | |
List<TypeListenerBinding> parentBindings = parent.getTypeListenerBindings(); | |
@@ -129,6 +133,17 @@ | |
result.addAll(listenerBindings); | |
return result; | |
} | |
+ | |
+ public void addBindingListener(BindingListenerBinding binding) { | |
+ bindListenerBindings.add(binding); | |
+ } | |
+ | |
+ public List<BindingListenerBinding> getBindingListenerBindings() { | |
+ return new ImmutableList.Builder<BindingListenerBinding>() | |
+ .addAll(parent.getBindingListenerBindings()) | |
+ .addAll(bindListenerBindings) | |
+ .build(); | |
+ } | |
public void blacklist(Key<?> key) { | |
parent.blacklist(key); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment