1/**
2 * Copyright (C) 2008 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.inject.internal;
18
19import static com.google.common.base.Preconditions.checkNotNull;
20
21import com.google.common.base.Preconditions;
22import com.google.common.collect.Lists;
23import com.google.common.collect.Maps;
24import com.google.inject.Binding;
25import com.google.inject.Key;
26import com.google.inject.Stage;
27import com.google.inject.TypeLiteral;
28import com.google.inject.spi.InjectionPoint;
29
30import java.util.Map;
31import java.util.Set;
32import java.util.concurrent.CountDownLatch;
33
34/**
35 * Manages and injects instances at injector-creation time. This is made more complicated by
36 * instances that request other instances while they're being injected. We overcome this by using
37 * {@link Initializable}, which attempts to perform injection before use.
38 *
39 * @author jessewilson@google.com (Jesse Wilson)
40 */
41final class Initializer {
42
43  /** the only thread that we'll use to inject members. */
44  private final Thread creatingThread = Thread.currentThread();
45
46  /** zero means everything is injected. */
47  private final CountDownLatch ready = new CountDownLatch(1);
48
49  /** Maps from instances that need injection to the MembersInjector that will inject them. */
50  private final Map<Object, MembersInjectorImpl<?>> pendingMembersInjectors =
51      Maps.newIdentityHashMap();
52
53  /** Maps instances that need injection to a source that registered them */
54  private final Map<Object, InjectableReference<?>> pendingInjection = Maps.newIdentityHashMap();
55
56  /**
57   * Registers an instance for member injection when that step is performed.
58   *
59   * @param instance an instance that optionally has members to be injected (each annotated with
60   *      @Inject).
61   * @param binding the binding that caused this initializable to be created, if it exists.
62   * @param source the source location that this injection was requested
63   */
64  <T> Initializable<T> requestInjection(InjectorImpl injector, T instance, Binding<T> binding,
65      Object source, Set<InjectionPoint> injectionPoints) {
66    checkNotNull(source);
67
68    ProvisionListenerStackCallback<T> provisionCallback =
69        binding == null ? null : injector.provisionListenerStore.get(binding);
70
71    // short circuit if the object has no injections or listeners.
72    if (instance == null || (injectionPoints.isEmpty()
73        && !injector.membersInjectorStore.hasTypeListeners()
74        && (provisionCallback == null || !provisionCallback.hasListeners()))) {
75      return Initializables.of(instance);
76    }
77
78    InjectableReference<T> initializable = new InjectableReference<T>(
79        injector, instance, binding == null ? null : binding.getKey(), provisionCallback, source);
80    pendingInjection.put(instance, initializable);
81    return initializable;
82  }
83
84  /**
85   * Prepares member injectors for all injected instances. This prompts Guice to do static analysis
86   * on the injected instances.
87   */
88  void validateOustandingInjections(Errors errors) {
89    for (InjectableReference<?> reference : pendingInjection.values()) {
90      try {
91        pendingMembersInjectors.put(reference.instance, reference.validate(errors));
92      } catch (ErrorsException e) {
93        errors.merge(e.getErrors());
94      }
95    }
96  }
97
98  /**
99   * Performs creation-time injections on all objects that require it. Whenever fulfilling an
100   * injection depends on another object that requires injection, we inject it first. If the two
101   * instances are codependent (directly or transitively), ordering of injection is arbitrary.
102   */
103  void injectAll(final Errors errors) {
104    // loop over a defensive copy since ensureInjected() mutates the set. Unfortunately, that copy
105    // is made complicated by a bug in IBM's JDK, wherein entrySet().toArray(Object[]) doesn't work
106    for (InjectableReference<?> reference : Lists.newArrayList(pendingInjection.values())) {
107      try {
108        reference.get(errors);
109      } catch (ErrorsException e) {
110        errors.merge(e.getErrors());
111      }
112    }
113
114    if (!pendingInjection.isEmpty()) {
115      throw new AssertionError("Failed to satisfy " + pendingInjection);
116    }
117
118    ready.countDown();
119  }
120
121  private class InjectableReference<T> implements Initializable<T> {
122    private final InjectorImpl injector;
123    private final T instance;
124    private final Object source;
125    private final Key<T> key;
126    private final ProvisionListenerStackCallback<T> provisionCallback;
127
128    public InjectableReference(InjectorImpl injector, T instance, Key<T> key,
129        ProvisionListenerStackCallback<T> provisionCallback, Object source) {
130      this.injector = injector;
131      this.key = key; // possibly null!
132      this.provisionCallback = provisionCallback; // possibly null!
133      this.instance = checkNotNull(instance, "instance");
134      this.source = checkNotNull(source, "source");
135    }
136
137    public MembersInjectorImpl<T> validate(Errors errors) throws ErrorsException {
138      @SuppressWarnings("unchecked") // the type of 'T' is a TypeLiteral<T>
139          TypeLiteral<T> type = TypeLiteral.get((Class<T>) instance.getClass());
140      return injector.membersInjectorStore.get(type, errors.withSource(source));
141    }
142
143    /**
144     * Reentrant. If {@code instance} was registered for injection at injector-creation time, this
145     * method will ensure that all its members have been injected before returning.
146     */
147    public T get(Errors errors) throws ErrorsException {
148      if (ready.getCount() == 0) {
149        return instance;
150      }
151
152      // just wait for everything to be injected by another thread
153      if (Thread.currentThread() != creatingThread) {
154        try {
155          ready.await();
156          return instance;
157        } catch (InterruptedException e) {
158          // Give up, since we don't know if our injection is ready
159          throw new RuntimeException(e);
160        }
161      }
162
163      // toInject needs injection, do it right away. we only do this once, even if it fails
164      if (pendingInjection.remove(instance) != null) {
165        // safe because we only insert a members injector for the appropriate instance
166        @SuppressWarnings("unchecked")
167        MembersInjectorImpl<T> membersInjector =
168            (MembersInjectorImpl<T>)pendingMembersInjectors.remove(instance);
169        Preconditions.checkState(membersInjector != null,
170            "No membersInjector available for instance: %s, from key: %s", instance, key);
171
172        // if in Stage.TOOL, we only want to inject & notify toolable injection points.
173        // (otherwise we'll inject all of them)
174        membersInjector.injectAndNotify(instance,
175            errors.withSource(source),
176            key,
177            provisionCallback,
178            source,
179            injector.options.stage == Stage.TOOL);
180      }
181
182      return instance;
183    }
184
185    @Override public String toString() {
186      return instance.toString();
187    }
188  }
189}
190