Skip to content

Commit

Permalink
Add support for ProvisionListeners to notify on toInstance & constant…
Browse files Browse the repository at this point in the history
… bindings.

---------------------
Manually synced.
COMMIT=41634417
  • Loading branch information
cgruber committed May 16, 2013
1 parent a1867f8 commit bf2b16c
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -51,8 +52,8 @@ abstract class AbstractBindingProcessor extends AbstractProcessor {
Module.class,
Provider.class,
Scope.class,
Stage.class,
TypeLiteral.class);
// TODO(jessewilson): fix BuiltInModule, then add Stage

protected final ProcessedBindingData bindingData;

Expand Down
4 changes: 3 additions & 1 deletion core/src/com/google/inject/internal/BindingProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,10 @@ public Boolean visit(InstanceBinding<? extends T> binding) {
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);
Expand Down
30 changes: 21 additions & 9 deletions core/src/com/google/inject/internal/Initializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -57,21 +58,25 @@ final class Initializer {
*
* @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;
}
Expand Down Expand Up @@ -118,10 +123,13 @@ private class InjectableReference<T> implements Initializable<T> {
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");
}
Expand Down Expand Up @@ -163,7 +171,11 @@ public T get(Errors errors) throws ErrorsException {

// 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);
}

Expand Down
22 changes: 14 additions & 8 deletions core/src/com/google/inject/internal/InjectorShell.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ List<InjectorShell> build(
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));

Expand Down Expand Up @@ -174,6 +174,7 @@ List<InjectorShell> build(
new TypeConverterBindingProcessor(errors).process(injector, elements);
stopwatch.resetAndLog("Converters creation");

bindStage(injector, stage);
bindInjector(injector);
bindLogger(injector);

Expand Down Expand Up @@ -270,16 +271,21 @@ public String toString() {
}
}

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);
}
Expand Down
24 changes: 19 additions & 5 deletions core/src/com/google/inject/internal/MembersInjectorImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -58,26 +59,39 @@ public ImmutableList<SingleMemberInjector> getMemberInjectors() {
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());
}

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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,33 @@

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.
*
* @author [email protected] (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
Expand All @@ -52,7 +63,12 @@ public ProvisionListenerStackCallback<?> apply(KeyBinding key) {
*/
@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();
}

/**
Expand Down Expand Up @@ -82,8 +98,10 @@ private <T> ProvisionListenerStackCallback<T> create(Binding<T> binding) {
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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()) {
Expand Down
4 changes: 3 additions & 1 deletion core/src/com/google/inject/spi/ProvisionListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ public interface ProvisionListener {
* 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.
Expand Down
2 changes: 2 additions & 0 deletions core/test/com/google/inject/BinderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ protected void configure() {
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));
}
Expand All @@ -471,6 +472,7 @@ protected void configure() {
"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.");
}
Expand Down
3 changes: 1 addition & 2 deletions core/test/com/google/inject/BindingTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,7 @@ protected void configure() {
}
});

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 {
Expand Down
Loading

0 comments on commit bf2b16c

Please sign in to comment.