blob: 2ab4b0fe50c80599112f67eabe9a586d1e4a2ba7 [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.common.collect.Multimap;
import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.CycleDetectingLock.CycleDetectingLockFactory;
import com.google.inject.spi.InjectionPoint;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
/**
* 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 {
/** Is set to true once {@link #validateOustandingInjections} is called. */
private volatile boolean validationStarted = false;
/**
* Allows us to detect circular dependencies. It's only used during injectable reference
* initialization. After initialization direct access through volatile field is used.
*/
private final CycleDetectingLockFactory<Class<?>> cycleDetectingLockFactory =
new CycleDetectingLockFactory<Class<?>>();
/**
* Instances that need injection during injector creation to a source that registered them. New
* references added before {@link #validateOustandingInjections}. Cleared up in {@link
* #injectAll}.
*/
private final List<InjectableReference<?>> pendingInjections = Lists.newArrayList();
/**
* Map that guarantees that no instance would get two references. New references added before
* {@link #validateOustandingInjections}. Cleared up in {@link #validateOustandingInjections}.
*/
private final IdentityHashMap<Object, InjectableReference<?>> initializablesCache =
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);
Preconditions.checkState(
!validationStarted, "Member injection could not be requested after validation is started");
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)) {
return Initializables.of(instance);
}
if (initializablesCache.containsKey(instance)) {
@SuppressWarnings("unchecked") // Map from T to InjectableReference<T>
Initializable<T> cached = (Initializable<T>) initializablesCache.get(instance);
return cached;
}
InjectableReference<T> injectableReference =
new InjectableReference<T>(
injector,
instance,
binding == null ? null : binding.getKey(),
provisionCallback,
source,
cycleDetectingLockFactory.create(instance.getClass()));
initializablesCache.put(instance, injectableReference);
pendingInjections.add(injectableReference);
return injectableReference;
}
/**
* Prepares member injectors for all injected instances. This prompts Guice to do static analysis
* on the injected instances.
*/
void validateOustandingInjections(Errors errors) {
validationStarted = true;
initializablesCache.clear();
for (InjectableReference<?> reference : pendingInjections) {
try {
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) {
Preconditions.checkState(validationStarted, "Validation should be done before injection");
for (InjectableReference<?> reference : pendingInjections) {
try {
reference.get();
} catch (InternalProvisionException ipe) {
errors.merge(ipe);
}
}
pendingInjections.clear();
}
private enum InjectableReferenceState {
NEW,
VALIDATED,
INJECTING,
READY
}
private static class InjectableReference<T> implements Initializable<T> {
private volatile InjectableReferenceState state = InjectableReferenceState.NEW;
private volatile MembersInjectorImpl<T> membersInjector = null;
private final InjectorImpl injector;
private final T instance;
private final Object source;
private final Key<T> key;
private final ProvisionListenerStackCallback<T> provisionCallback;
private final CycleDetectingLock<?> lock;
public InjectableReference(
InjectorImpl injector,
T instance,
Key<T> key,
ProvisionListenerStackCallback<T> provisionCallback,
Object source,
CycleDetectingLock<?> lock) {
this.injector = injector;
this.key = key; // possibly null!
this.provisionCallback = provisionCallback; // possibly null!
this.instance = checkNotNull(instance, "instance");
this.source = checkNotNull(source, "source");
this.lock = checkNotNull(lock, "lock");
}
public void validate(Errors errors) throws ErrorsException {
@SuppressWarnings("unchecked") // the type of 'T' is a TypeLiteral<T>
TypeLiteral<T> type = TypeLiteral.get((Class<T>) instance.getClass());
membersInjector = injector.membersInjectorStore.get(type, errors.withSource(source));
Preconditions.checkNotNull(
membersInjector,
"No membersInjector available for instance: %s, from key: %s",
instance,
key);
state = InjectableReferenceState.VALIDATED;
}
/**
* 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.
*/
@Override
public T get() throws InternalProvisionException {
// skipping acquiring lock if initialization is already finished
if (state == InjectableReferenceState.READY) {
return instance;
}
// acquire lock for current binding to initialize an instance
Multimap<?, ?> lockCycle = lock.lockOrDetectPotentialLocksCycle();
if (!lockCycle.isEmpty()) {
// Potential deadlock detected and creation lock is not taken.
// According to injectAll()'s contract return non-initialized instance.
// This condition should not be possible under the current Guice implementation.
// This clause exists for defensive programming purposes.
// Reasoning:
// get() is called either directly from injectAll(), holds no locks and can not create
// a cycle, or it is called through a singleton scope, which resolves deadlocks by itself.
// Before calling get() object has to be requested for injection.
// Initializer.requestInjection() is called either for constant object bindings, which wrap
// creation into a Singleton scope, or from Binder.requestInjection(), which
// has to use Singleton scope to reuse the same InjectableReference to potentially
// create a lock cycle.
return instance;
}
try {
// lock acquired, current thread owns this instance initialization
switch (state) {
case READY:
return instance;
// When instance depends on itself in the same thread potential dead lock
// is not detected. We have to prevent a stack overflow and we use
// an "injecting" stage to short-circuit a call.
case INJECTING:
return instance;
case VALIDATED:
state = InjectableReferenceState.INJECTING;
break;
case NEW:
throw new IllegalStateException("InjectableReference is not validated yet");
default:
throw new IllegalStateException("Unknown state: " + state);
}
// if in Stage.TOOL, we only want to inject & notify toolable injection points.
// (otherwise we'll inject all of them)
try {
membersInjector.injectAndNotify(
instance, key, provisionCallback, source, injector.options.stage == Stage.TOOL);
} catch (InternalProvisionException ipe) {
throw ipe.addSource(source);
}
// mark instance as ready to skip a lock on subsequent calls
state = InjectableReferenceState.READY;
return instance;
} finally {
// always release our creation lock, even on failures
lock.unlock();
}
}
@Override
public String toString() {
return instance.toString();
}
}
}