FactoryProvider2.java revision 141f800c09d66898ce04c7684330e1e9dc8a31ab
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.assistedinject;
18
19import com.google.inject.AbstractModule;
20import com.google.inject.Binder;
21import com.google.inject.Binding;
22import com.google.inject.ConfigurationException;
23import com.google.inject.Inject;
24import com.google.inject.Injector;
25import com.google.inject.Key;
26import com.google.inject.Module;
27import com.google.inject.Provider;
28import com.google.inject.ProvisionException;
29import com.google.inject.TypeLiteral;
30import static com.google.inject.internal.Annotations.getKey;
31import com.google.inject.internal.Errors;
32import com.google.inject.internal.ErrorsException;
33import com.google.inject.internal.ImmutableList;
34import com.google.inject.internal.ImmutableMap;
35import com.google.inject.internal.ToStringBuilder;
36
37import static com.google.inject.internal.Iterables.getOnlyElement;
38import com.google.inject.internal.Lists;
39import static com.google.inject.internal.Preconditions.checkState;
40
41import com.google.inject.spi.Dependency;
42import com.google.inject.spi.InjectionPoint;
43import com.google.inject.spi.Message;
44import com.google.inject.spi.Toolable;
45import com.google.inject.util.Providers;
46import java.lang.annotation.Annotation;
47import java.lang.reflect.Constructor;
48import java.lang.reflect.InvocationHandler;
49import java.lang.reflect.Method;
50import java.lang.reflect.Proxy;
51import java.util.Arrays;
52import java.util.Collections;
53import java.util.List;
54import java.util.Map;
55
56/**
57 * The newer implementation of factory provider. This implementation uses a child injector to
58 * create values.
59 *
60 * @author jessewilson@google.com (Jesse Wilson)
61 * @author dtm@google.com (Daniel Martin)
62 * @author schmitt@google.com (Peter Schmitt)
63 * @author sameb@google.com (Sam Berlin)
64 */
65final class FactoryProvider2<F> implements InvocationHandler, Provider<F> {
66
67  /** if a factory method parameter isn't annotated, it gets this annotation. */
68  static final Assisted DEFAULT_ANNOTATION = new Assisted() {
69    public String value() {
70      return "";
71    }
72
73    public Class<? extends Annotation> annotationType() {
74      return Assisted.class;
75    }
76
77    @Override public boolean equals(Object o) {
78      return o instanceof Assisted
79          && ((Assisted) o).value().equals("");
80    }
81
82    @Override public int hashCode() {
83      return 127 * "value".hashCode() ^ "".hashCode();
84    }
85
86    @Override public String toString() {
87      return "@" + Assisted.class.getName() + "(value=)";
88    }
89  };
90
91  /** All the data necessary to perform an assisted inject. */
92  private static class AssistData {
93    /** the constructor the implementation is constructed with. */
94    final Constructor<?> constructor;
95    /** the return type in the factory method that the constructor is bound to. */
96    final Key<?> returnType;
97    /** the parameters in the factory method associated with this data. */
98    final ImmutableList<Key<?>> paramTypes;
99
100    /** true if {@link #validForOptimizedAssistedInject} returned true. */
101    final boolean optimized;
102    /** the list of optimized providers, empty if not optimized. */
103    final List<ThreadLocalProvider> providers;
104    /** used to perform optimized factory creations. */
105    volatile Binding<?> cachedBinding; // TODO: volatile necessary?
106
107    AssistData(Constructor<?> constructor, Key<?> returnType,
108        ImmutableList<Key<?>> paramTypes, boolean optimized,
109        List<ThreadLocalProvider> providers) {
110      this.constructor = constructor;
111      this.returnType = returnType;
112      this.paramTypes = paramTypes;
113      this.optimized = optimized;
114      this.providers = providers;
115    }
116
117    @Override
118    public String toString() {
119      return new ToStringBuilder(getClass())
120        .add("ctor", constructor)
121        .add("return type", returnType)
122        .add("param type", paramTypes)
123        .add("optimized", optimized)
124        .add("providers", providers)
125        .add("cached binding", cachedBinding)
126        .toString();
127
128    }
129  }
130
131  /** the produced type, or null if all methods return concrete types */
132  private final BindingCollector collector;
133  private final ImmutableMap<Method, AssistData> assistDataByMethod;
134
135  /** the hosting injector, or null if we haven't been initialized yet */
136  private Injector injector;
137
138  /** the factory interface, implemented and provided */
139  private final F factory;
140
141  /**
142   * @param factoryType a Java interface that defines one or more create methods.
143   * @param collector binding configuration that maps method return types to
144   *    implementation types.
145   */
146  FactoryProvider2(TypeLiteral<F> factoryType, BindingCollector collector) {
147    this.collector = collector;
148
149    Errors errors = new Errors();
150
151    @SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class<T>
152    Class<F> factoryRawType = (Class) factoryType.getRawType();
153
154    try {
155      ImmutableMap.Builder<Method, AssistData> assistDataBuilder = ImmutableMap.builder();
156      // TODO: also grab methods from superinterfaces
157      for (Method method : factoryRawType.getMethods()) {
158        Key<?> returnType = getKey(
159            factoryType.getReturnType(method), method, method.getAnnotations(), errors);
160        List<TypeLiteral<?>> params = factoryType.getParameterTypes(method);
161        Annotation[][] paramAnnotations = method.getParameterAnnotations();
162        int p = 0;
163        List<Key<?>> keys = Lists.newArrayList();
164        for (TypeLiteral<?> param : params) {
165          Key<?> paramKey = getKey(param, method, paramAnnotations[p++], errors);
166          keys.add(assistKey(method, paramKey, errors));
167        }
168        ImmutableList<Key<?>> immutableParamList = ImmutableList.copyOf(keys);
169
170        // try to match up the method to the constructor
171        TypeLiteral<?> implementation = collector.getBindings().get(returnType);
172        if(implementation == null) {
173          implementation = returnType.getTypeLiteral();
174        }
175        // TODO: Support finding the proper constructor per method...
176        // Right now, this will fail later on if it's the wrong constructor, because it will
177        // be missing necessary bindings.
178        InjectionPoint ctorInjectionPoint = null;
179        Constructor<?> constructor = null;
180        try {
181          ctorInjectionPoint = InjectionPoint.forConstructorOf(implementation);
182        } catch(ConfigurationException ce) {
183          // There are two reasons this could throw.
184          // 1) The implementation is an interface and is forwarded (explicitly or implicitly)
185          //    to another binding (see FactoryModuleBuildTest.test[Implicit|Explicit]ForwardingAssistedBinding.
186          //    In this case, things are OK and the exception is right to be ignored.
187          // 2) The implementation has something wrong with its constructors (two @injects, invalid ctor, etc..)
188          //    In this case, by having a null constructor we let the proper exception be recreated later on.
189        }
190        if(ctorInjectionPoint != null) {
191          constructor = (Constructor)ctorInjectionPoint.getMember();
192        }
193
194        List<ThreadLocalProvider> providers = Collections.emptyList();
195        boolean optimized = false;
196        // Now go through all dependencies of the implementation and see if it is OK to
197        // use an optimized form of assistedinject2.  The optimized form requires that
198        // all injections directly inject the object itself (and not a Provider of the object,
199        // or an Injector), because it caches a single child injector and mutates the Provider
200        // of the arguments in a ThreadLocal.
201        if(validForOptimizedAssistedInject(ctorInjectionPoint, implementation)) {
202          ImmutableList.Builder<ThreadLocalProvider> providerListBuilder = ImmutableList.builder();
203          for(int i = 0; i < params.size(); i++) {
204            providerListBuilder.add(new ThreadLocalProvider());
205          }
206          providers = providerListBuilder.build();
207          optimized = true;
208        }
209        assistDataBuilder.put(method, new AssistData(constructor, returnType, immutableParamList, optimized, providers));
210      }
211
212      // If we generated any errors (from finding matching constructors, for instance), throw an exception.
213      if(errors.hasErrors()) {
214        throw errors.toException();
215      }
216
217      assistDataByMethod = assistDataBuilder.build();
218    } catch (ErrorsException e) {
219      throw new ConfigurationException(e.getErrors().getMessages());
220    }
221
222    factory = factoryRawType.cast(Proxy.newProxyInstance(factoryRawType.getClassLoader(),
223        new Class[] { factoryRawType }, this));
224  }
225
226  public F get() {
227    return factory;
228  }
229
230  /**
231   * Returns true if the implementation & constructor are suitable for an
232   * optimized version of AssistedInject. The optimized version caches the
233   * binding & uses a ThreadLocal Provider, so can only be applied if the
234   * assisted bindings are immediately provided. This looks for hints that the
235   * values may be lazily retrieved, by looking for injections of Injector or a
236   * Provider for the assisted values.
237   */
238  private boolean validForOptimizedAssistedInject(InjectionPoint ctorPoint, TypeLiteral<?> implementation) {
239    if(ctorPoint != null) {
240      for(Dependency<?> dep : ctorPoint.getDependencies()) {
241        if(isInjectorOrAssistedProvider(dep)) {
242          return false;
243        }
244      }
245    }
246    if(!implementation.getRawType().isInterface()) {
247      for(InjectionPoint ip : InjectionPoint.forInstanceMethodsAndFields(implementation)) {
248        for(Dependency<?> dep : ip.getDependencies()) {
249          if(isInjectorOrAssistedProvider(dep)) {
250            return false;
251          }
252        }
253      }
254    }
255    return true;
256  }
257
258  /**
259   * Returns true if the dependency is for {@link Injector} or if the dependency
260   * is a {@link Provider} for a parameter that is {@literal @}{@link Assisted}.
261   */
262  private boolean isInjectorOrAssistedProvider(Dependency<?> dependency) {
263    Class annotationType = dependency.getKey().getAnnotationType();
264    if (annotationType != null && annotationType.equals(Assisted.class)) { // If it's assisted..
265      if (dependency.getKey().getTypeLiteral().getRawType().equals(Provider.class)) { // And a Provider...
266        return true;
267      }
268    } else if (dependency.getKey().getTypeLiteral().getRawType().equals(Injector.class)) { // If it's the Injector...
269      return true;
270    }
271    return false;
272  }
273
274  /**
275   * Returns a key similar to {@code key}, but with an {@literal @}Assisted binding annotation.
276   * This fails if another binding annotation is clobbered in the process. If the key already has
277   * the {@literal @}Assisted annotation, it is returned as-is to preserve any String value.
278   */
279  private <T> Key<T> assistKey(Method method, Key<T> key, Errors errors) throws ErrorsException {
280    if (key.getAnnotationType() == null) {
281      return Key.get(key.getTypeLiteral(), DEFAULT_ANNOTATION);
282    } else if (key.getAnnotationType() == Assisted.class) {
283      return key;
284    } else {
285      errors.withSource(method).addMessage(
286          "Only @Assisted is allowed for factory parameters, but found @%s",
287          key.getAnnotationType());
288      throw errors.toException();
289    }
290  }
291
292  /**
293   * At injector-creation time, we initialize the invocation handler. At this time we make sure
294   * all factory methods will be able to build the target types.
295   */
296  @Inject @Toolable
297  void initialize(Injector injector) {
298    if (this.injector != null) {
299      throw new ConfigurationException(ImmutableList.of(new Message(FactoryProvider2.class,
300          "Factories.create() factories may only be used in one Injector!")));
301    }
302
303    this.injector = injector;
304
305    for (Map.Entry<Method, AssistData> entry : assistDataByMethod.entrySet()) {
306      Method method = entry.getKey();
307      AssistData data = entry.getValue();
308      Object[] args;
309      if(!data.optimized) {
310        args = new Object[method.getParameterTypes().length];
311        Arrays.fill(args, "dummy object for validating Factories");
312      } else {
313        args = null; // won't be used -- instead will bind to data.providers.
314      }
315      getBindingFromNewInjector(method, args, data); // throws if the binding isn't properly configured
316    }
317  }
318
319  /**
320   * Creates a child injector that binds the args, and returns the binding for the method's result.
321   */
322  public Binding<?> getBindingFromNewInjector(final Method method, final Object[] args, final AssistData data) {
323    checkState(injector != null,
324        "Factories.create() factories cannot be used until they're initialized by Guice.");
325
326    final Key<?> returnType = data.returnType;
327
328    // We ignore any pre-existing binding annotation.
329    final Key<?> assistedReturnType = Key.get(returnType.getTypeLiteral(), Assisted.class);
330
331    Module assistedModule = new AbstractModule() {
332      @SuppressWarnings("unchecked") // raw keys are necessary for the args array and return value
333      protected void configure() {
334        Binder binder = binder().withSource(method);
335
336        int p = 0;
337        if(!data.optimized) {
338          for (Key<?> paramKey : data.paramTypes) {
339            // Wrap in a Provider to cover null, and to prevent Guice from injecting the parameter
340            binder.bind((Key) paramKey).toProvider(Providers.of(args[p++]));
341          }
342        } else {
343          for (Key<?> paramKey : data.paramTypes) {
344            // Bind to our ThreadLocalProviders.
345            binder.bind((Key) paramKey).toProvider(data.providers.get(p++));
346          }
347        }
348
349
350        Constructor<?> constructor = data.constructor;
351        // If the injector already has a binding for the return type, don't
352        // bother binding to a specific constructor. Otherwise, there could be
353        // bugs where an implicit binding isn't used (or an explicitly forwarded
354        // binding isn't used)
355        if (injector.getExistingBinding(returnType) != null) {
356          constructor = null;
357        }
358
359        TypeLiteral<?> implementation = collector.getBindings().get(returnType);
360        if (implementation != null) {
361          if(constructor == null) {
362            binder.bind(assistedReturnType).to((TypeLiteral)implementation);
363          } else {
364            binder.bind(assistedReturnType).toConstructor((Constructor)constructor, (TypeLiteral)implementation);
365          }
366        } else {
367          // no implementation, but need to bind from assisted key to actual key.
368          if(constructor == null) {
369            binder.bind(assistedReturnType).to((Key)returnType);
370          } else {
371            binder.bind(assistedReturnType).toConstructor((Constructor)constructor);
372          }
373        }
374      }
375    };
376
377    Injector forCreate = injector.createChildInjector(assistedModule);
378    Binding binding = forCreate.getBinding(assistedReturnType);
379    // If we have providers cached in data, cache the binding for future optimizations.
380    if(data.optimized) {
381      data.cachedBinding = binding;
382    }
383    return binding;
384  }
385
386  /**
387   * When a factory method is invoked, we create a child injector that binds all parameters, then
388   * use that to get an instance of the return type.
389   */
390  public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
391    if (method.getDeclaringClass() == Object.class) {
392      return method.invoke(this, args);
393    }
394
395    AssistData data = assistDataByMethod.get(method);
396    Provider<?> provider;
397    if(data.cachedBinding != null) { // Try to get optimized form...
398      provider = data.cachedBinding.getProvider();
399    } else {
400      provider = getBindingFromNewInjector(method, args, data).getProvider();
401    }
402    try {
403      int p = 0;
404      for(ThreadLocalProvider tlp : data.providers) {
405        tlp.set(args[p++]);
406      }
407      return provider.get();
408    } catch (ProvisionException e) {
409      // if this is an exception declared by the factory method, throw it as-is
410      if (e.getErrorMessages().size() == 1) {
411        Message onlyError = getOnlyElement(e.getErrorMessages());
412        Throwable cause = onlyError.getCause();
413        if (cause != null && canRethrow(method, cause)) {
414          throw cause;
415        }
416      }
417      throw e;
418    } finally {
419      for(ThreadLocalProvider tlp : data.providers) {
420        tlp.remove();
421      }
422    }
423  }
424
425  @Override public String toString() {
426    return factory.getClass().getInterfaces()[0].getName();
427  }
428
429  @Override public boolean equals(Object o) {
430    return o == this || o == factory;
431  }
432
433  /** Returns true if {@code thrown} can be thrown by {@code invoked} without wrapping. */
434  static boolean canRethrow(Method invoked, Throwable thrown) {
435    if (thrown instanceof Error || thrown instanceof RuntimeException) {
436      return true;
437    }
438
439    for (Class<?> declared : invoked.getExceptionTypes()) {
440      if (declared.isInstance(thrown)) {
441        return true;
442      }
443    }
444
445    return false;
446  }
447
448  // not <T> because we'll never know and this is easier than suppressing warnings.
449  private static class ThreadLocalProvider extends ThreadLocal<Object> implements Provider<Object> {
450    @Override
451    protected Object initialValue() {
452      throw new IllegalStateException(
453          "Cannot use optimized @Assisted provider outside the scope of the constructor."
454              + " (This should never happen.  If it does, please report it.)");
455    }
456  }
457}
458