All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.inject.internal.Initializer Maven / Gradle / Ivy

package com.google.inject.internal;

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.spi.InjectionPoint;

import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * 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.
 *
 */
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 CycleDetectingLock.CycleDetectingLockFactory> cycleDetectingLockFactory =
            new CycleDetectingLock.CycleDetectingLockFactory<>();

    /**
     * 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> 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> 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
     * @param binding  the binding that caused this initializable to be created, if it exists.
     * @param source   the source location that this injection was requested
     * @Inject).
     */
     Initializable requestInjection(InjectorImpl injector, T instance, Binding binding,
                                          Object source, Set injectionPoints) {
        checkNotNull(source);
        Preconditions.checkState(
                !validationStarted, "Member injection could not be requested after validation is started");
        ProvisionListenerStackCallback 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
                    Initializable cached = (Initializable) initializablesCache.get(instance);
            return cached;
        }

        InjectableReference injectableReference =
                new InjectableReference(
                        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 implements Initializable {
        private volatile InjectableReferenceState state = InjectableReferenceState.NEW;
        private volatile MembersInjectorImpl membersInjector = null;

        private final InjectorImpl injector;
        private final T instance;
        private final Object source;
        private final Key key;
        private final ProvisionListenerStackCallback provisionCallback;
        private final CycleDetectingLock lock;

        public InjectableReference(InjectorImpl injector, T instance, Key key,
                                   ProvisionListenerStackCallback 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
            TypeLiteral type = TypeLiteral.get((Class) 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();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy