Skip to content

Instantly share code, notes, and snippets.

@gissuebot
Created July 7, 2014 17:53
Show Gist options
  • Save gissuebot/a9759687faed5e504537 to your computer and use it in GitHub Desktop.
Save gissuebot/a9759687faed5e504537 to your computer and use it in GitHub Desktop.
Migrated attachment for Guice issue 38, comment 23
### 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