/* * 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(errors); } catch (ErrorsException e) { errors.merge(e.getErrors()); } } 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(Errors errors) throws ErrorsException { // 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) membersInjector.injectAndNotify( instance, errors.withSource(source), key, provisionCallback, source, injector.options.stage == Stage.TOOL); // 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(); } } }