blob: ff83ea9bba582fcedb810337c44dabeef25a5ff1 [file] [log] [blame]
/**
* Copyright (C) 2008 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 static com.google.common.base.Preconditions.checkNotNull;
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;
import com.google.inject.spi.InjectionPoint;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
/**
* Manages and injects instances at injector-creation time. This is made more complicated by
* instances that request other instances while they're being injected. We overcome this by using
* {@link Initializable}, which attempts to perform injection before use.
*
* @author jessewilson@google.com (Jesse Wilson)
*/
final class Initializer {
/** the only thread that we'll use to inject members. */
private final Thread creatingThread = Thread.currentThread();
/** zero means everything is injected. */
private final CountDownLatch ready = new CountDownLatch(1);
/** Maps from instances that need injection to the MembersInjector that will inject them. */
private final Map<Object, MembersInjectorImpl<?>> pendingMembersInjectors =
Maps.newIdentityHashMap();
/** Maps instances that need injection to a source that registered them */
private final Map<Object, InjectableReference<?>> pendingInjection = Maps.newIdentityHashMap();
/**
* Registers an instance for member injection when that step is performed.
*
* @param instance an instance that optionally has members to be injected (each annotated with
* @Inject).
* @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, Binding<T> binding,
Object source, Set<InjectionPoint> injectionPoints) {
checkNotNull(source);
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, binding == null ? null : binding.getKey(), provisionCallback, source);
pendingInjection.put(instance, initializable);
return initializable;
}
/**
* Prepares member injectors for all injected instances. This prompts Guice to do static analysis
* on the injected instances.
*/
void validateOustandingInjections(Errors errors) {
for (InjectableReference<?> reference : pendingInjection.values()) {
try {
pendingMembersInjectors.put(reference.instance, reference.validate(errors));
} catch (ErrorsException e) {
errors.merge(e.getErrors());
}
}
}
/**
* Performs creation-time injections on all objects that require it. Whenever fulfilling an
* injection depends on another object that requires injection, we inject it first. If the two
* instances are codependent (directly or transitively), ordering of injection is arbitrary.
*/
void injectAll(final Errors errors) {
// loop over a defensive copy since ensureInjected() mutates the set. Unfortunately, that copy
// is made complicated by a bug in IBM's JDK, wherein entrySet().toArray(Object[]) doesn't work
for (InjectableReference<?> reference : Lists.newArrayList(pendingInjection.values())) {
try {
reference.get(errors);
} catch (ErrorsException e) {
errors.merge(e.getErrors());
}
}
if (!pendingInjection.isEmpty()) {
throw new AssertionError("Failed to satisfy " + pendingInjection);
}
ready.countDown();
}
private class InjectableReference<T> implements Initializable<T> {
private final InjectorImpl injector;
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,
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");
}
public MembersInjectorImpl<T> validate(Errors errors) throws ErrorsException {
@SuppressWarnings("unchecked") // the type of 'T' is a TypeLiteral<T>
TypeLiteral<T> type = TypeLiteral.get((Class<T>) instance.getClass());
return injector.membersInjectorStore.get(type, errors.withSource(source));
}
/**
* Reentrant. If {@code instance} was registered for injection at injector-creation time, this
* method will ensure that all its members have been injected before returning.
*/
public T get(Errors errors) throws ErrorsException {
if (ready.getCount() == 0) {
return instance;
}
// just wait for everything to be injected by another thread
if (Thread.currentThread() != creatingThread) {
try {
ready.await();
return instance;
} catch (InterruptedException e) {
// Give up, since we don't know if our injection is ready
throw new RuntimeException(e);
}
}
// toInject needs injection, do it right away. we only do this once, even if it fails
if (pendingInjection.remove(instance) != null) {
// safe because we only insert a members injector for the appropriate instance
@SuppressWarnings("unchecked")
MembersInjectorImpl<T> membersInjector =
(MembersInjectorImpl<T>)pendingMembersInjectors.remove(instance);
Preconditions.checkState(membersInjector != null,
"No membersInjector available for instance: %s, from key: %s", instance, key);
// 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,
provisionCallback,
source,
injector.options.stage == Stage.TOOL);
}
return instance;
}
@Override public String toString() {
return instance.toString();
}
}
}