FactoryProvider2.java revision b5a75ed3c72d772e7dc9f771a63b3e7226695919
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.BytecodeGen;
32import com.google.inject.internal.Errors;
33import com.google.inject.internal.ErrorsException;
34import com.google.inject.internal.util.Classes;
35import com.google.inject.internal.util.ImmutableList;
36import com.google.inject.internal.util.ImmutableMap;
37import com.google.inject.internal.util.Iterables;
38import com.google.inject.internal.util.ToStringBuilder;
39
40import static com.google.inject.internal.util.Iterables.getOnlyElement;
41import com.google.inject.internal.util.Lists;
42import static com.google.inject.internal.util.Preconditions.checkState;
43
44import com.google.inject.spi.Dependency;
45import com.google.inject.spi.InjectionPoint;
46import com.google.inject.spi.Message;
47import com.google.inject.spi.Toolable;
48import com.google.inject.util.Providers;
49import java.lang.annotation.Annotation;
50import java.lang.reflect.Constructor;
51import java.lang.reflect.InvocationHandler;
52import java.lang.reflect.Method;
53import java.lang.reflect.Modifier;
54import java.lang.reflect.Proxy;
55import java.util.Arrays;
56import java.util.Collection;
57import java.util.Collections;
58import java.util.List;
59import java.util.Map;
60
61/**
62 * The newer implementation of factory provider. This implementation uses a child injector to
63 * create values.
64 *
65 * @author jessewilson@google.com (Jesse Wilson)
66 * @author dtm@google.com (Daniel Martin)
67 * @author schmitt@google.com (Peter Schmitt)
68 * @author sameb@google.com (Sam Berlin)
69 */
70final class FactoryProvider2<F> implements InvocationHandler, Provider<F> {
71
72  /** if a factory method parameter isn't annotated, it gets this annotation. */
73  static final Assisted DEFAULT_ANNOTATION = new Assisted() {
74    public String value() {
75      return "";
76    }
77
78    public Class<? extends Annotation> annotationType() {
79      return Assisted.class;
80    }
81
82    @Override public boolean equals(Object o) {
83      return o instanceof Assisted
84          && ((Assisted) o).value().equals("");
85    }
86
87    @Override public int hashCode() {
88      return 127 * "value".hashCode() ^ "".hashCode();
89    }
90
91    @Override public String toString() {
92      return "@" + Assisted.class.getName() + "(value=)";
93    }
94  };
95
96  /** All the data necessary to perform an assisted inject. */
97  private static class AssistData {
98    /** the constructor the implementation is constructed with. */
99    final Constructor<?> constructor;
100    /** the return type in the factory method that the constructor is bound to. */
101    final Key<?> returnType;
102    /** the parameters in the factory method associated with this data. */
103    final ImmutableList<Key<?>> paramTypes;
104
105    /** true if {@link #validForOptimizedAssistedInject} returned true. */
106    final boolean optimized;
107    /** the list of optimized providers, empty if not optimized. */
108    final List<ThreadLocalProvider> providers;
109    /** used to perform optimized factory creations. */
110    volatile Binding<?> cachedBinding; // TODO: volatile necessary?
111
112    AssistData(Constructor<?> constructor, Key<?> returnType,
113        ImmutableList<Key<?>> paramTypes, boolean optimized,
114        List<ThreadLocalProvider> providers) {
115      this.constructor = constructor;
116      this.returnType = returnType;
117      this.paramTypes = paramTypes;
118      this.optimized = optimized;
119      this.providers = providers;
120    }
121
122    @Override
123    public String toString() {
124      return new ToStringBuilder(getClass())
125        .add("ctor", constructor)
126        .add("return type", returnType)
127        .add("param type", paramTypes)
128        .add("optimized", optimized)
129        .add("providers", providers)
130        .add("cached binding", cachedBinding)
131        .toString();
132
133    }
134  }
135
136  /** the produced type, or null if all methods return concrete types */
137  private final BindingCollector collector;
138  private final ImmutableMap<Method, AssistData> assistDataByMethod;
139
140  /** the hosting injector, or null if we haven't been initialized yet */
141  private Injector injector;
142
143  /** the factory interface, implemented and provided */
144  private final F factory;
145
146  /**
147   * @param factoryType a Java interface that defines one or more create methods.
148   * @param collector binding configuration that maps method return types to
149   *    implementation types.
150   */
151  FactoryProvider2(TypeLiteral<F> factoryType, BindingCollector collector) {
152    this.collector = collector;
153
154    Errors errors = new Errors();
155
156    @SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class<T>
157    Class<F> factoryRawType = (Class) factoryType.getRawType();
158
159    try {
160      ImmutableMap.Builder<Method, AssistData> assistDataBuilder = ImmutableMap.builder();
161      // TODO: also grab methods from superinterfaces
162      for (Method method : factoryRawType.getMethods()) {
163        TypeLiteral<?> returnTypeLiteral = factoryType.getReturnType(method);
164        Key<?> returnType;
165        try {
166          returnType = getKey(returnTypeLiteral, method, method.getAnnotations(), errors);
167        } catch(ConfigurationException ce) {
168          // If this was an error due to returnTypeLiteral not being specified, rephrase
169          // it as our factory not being specified, so it makes more sense to users.
170          if(isTypeNotSpecified(returnTypeLiteral, ce)) {
171            throw errors.keyNotFullySpecified(TypeLiteral.get(factoryRawType)).toException();
172          } else {
173            throw ce;
174          }
175        }
176        List<TypeLiteral<?>> params = factoryType.getParameterTypes(method);
177        Annotation[][] paramAnnotations = method.getParameterAnnotations();
178        int p = 0;
179        List<Key<?>> keys = Lists.newArrayList();
180        for (TypeLiteral<?> param : params) {
181          Key<?> paramKey = getKey(param, method, paramAnnotations[p++], errors);
182          Class<?> underlylingType = paramKey.getTypeLiteral().getRawType();
183          if (underlylingType.equals(Provider.class)
184              || underlylingType.equals(javax.inject.Provider.class)) {
185            errors.addMessage("A Provider may not be a type in a factory method of an AssistedInject."
186                    + "\n  Offending instance is parameter [%s] with key [%s] on method [%s]",
187                    p, paramKey, method);
188          }
189          keys.add(assistKey(method, paramKey, errors));
190        }
191        ImmutableList<Key<?>> immutableParamList = ImmutableList.copyOf(keys);
192
193        // try to match up the method to the constructor
194        TypeLiteral<?> implementation = collector.getBindings().get(returnType);
195        if(implementation == null) {
196          implementation = returnType.getTypeLiteral();
197        }
198        InjectionPoint ctorInjectionPoint;
199        try {
200          ctorInjectionPoint =
201            findMatchingConstructorInjectionPoint(method, returnType, implementation, immutableParamList);
202        } catch(ErrorsException ee) {
203          errors.merge(ee.getErrors());
204          continue;
205        }
206
207        Constructor<?> constructor = (Constructor)ctorInjectionPoint.getMember();
208        List<ThreadLocalProvider> providers = Collections.emptyList();
209        boolean optimized = false;
210        // Now go through all dependencies of the implementation and see if it is OK to
211        // use an optimized form of assistedinject2.  The optimized form requires that
212        // all injections directly inject the object itself (and not a Provider of the object,
213        // or an Injector), because it caches a single child injector and mutates the Provider
214        // of the arguments in a ThreadLocal.
215        if(validForOptimizedAssistedInject(ctorInjectionPoint, implementation)) {
216          ImmutableList.Builder<ThreadLocalProvider> providerListBuilder = ImmutableList.builder();
217          for(int i = 0; i < params.size(); i++) {
218            providerListBuilder.add(new ThreadLocalProvider());
219          }
220          providers = providerListBuilder.build();
221          optimized = true;
222        }
223        assistDataBuilder.put(method, new AssistData(constructor, returnType, immutableParamList, optimized, providers));
224      }
225
226      // If we generated any errors (from finding matching constructors, for instance), throw an exception.
227      if(errors.hasErrors()) {
228        throw errors.toException();
229      }
230
231      assistDataByMethod = assistDataBuilder.build();
232    } catch (ErrorsException e) {
233      throw new ConfigurationException(e.getErrors().getMessages());
234    }
235
236    factory = factoryRawType.cast(Proxy.newProxyInstance(BytecodeGen.getClassLoader(factoryRawType),
237        new Class[] { factoryRawType }, this));
238  }
239
240  public F get() {
241    return factory;
242  }
243
244  /**
245   * Returns true if the ConfigurationException is due to an error of TypeLiteral not being fully
246   * specified.
247   */
248  private boolean isTypeNotSpecified(TypeLiteral typeLiteral, ConfigurationException ce) {
249    Collection<Message> messages = ce.getErrorMessages();
250    if (messages.size() == 1) {
251      Message msg = Iterables.getOnlyElement(
252          new Errors().keyNotFullySpecified(typeLiteral).getMessages());
253      return msg.getMessage().equals(Iterables.getOnlyElement(messages).getMessage());
254    } else {
255      return false;
256    }
257  }
258
259  /**
260   * Finds a constructor suitable for the method.  If the implementation contained any constructors
261   * marked with {@link AssistedInject}, this requires all {@link Assisted} parameters to exactly
262   * match the parameters (in any order) listed in the method.  Otherwise, if no
263   * {@link AssistedInject} constructors exist, this will default to looking for an
264   * {@literal @}{@link Inject} constructor.
265   */
266  private InjectionPoint findMatchingConstructorInjectionPoint(
267      Method method, Key<?> returnType, TypeLiteral<?> implementation, List<Key<?>> paramList)
268      throws ErrorsException {
269    Errors errors = new Errors(method);
270    if(returnType.getTypeLiteral().equals(implementation)) {
271      errors = errors.withSource(implementation);
272    } else {
273      errors = errors.withSource(returnType).withSource(implementation);
274    }
275
276    Class<?> rawType = implementation.getRawType();
277    if (Modifier.isInterface(rawType.getModifiers())) {
278      errors.addMessage(
279          "%s is an interface, not a concrete class.  Unable to create AssistedInject factory.",
280          implementation);
281      throw errors.toException();
282    } else if (Modifier.isAbstract(rawType.getModifiers())) {
283      errors.addMessage(
284          "%s is abstract, not a concrete class.  Unable to create AssistedInject factory.",
285          implementation);
286      throw errors.toException();
287    } else if (Classes.isInnerClass(rawType)) {
288      errors.cannotInjectInnerClass(rawType);
289      throw errors.toException();
290    }
291
292    Constructor<?> matchingConstructor = null;
293    boolean anyAssistedInjectConstructors = false;
294    // Look for AssistedInject constructors...
295    for (Constructor<?> constructor : rawType.getDeclaredConstructors()) {
296      if (constructor.isAnnotationPresent(AssistedInject.class)) {
297        anyAssistedInjectConstructors = true;
298        if (constructorHasMatchingParams(implementation, constructor, paramList, errors)) {
299          if (matchingConstructor != null) {
300            errors
301                .addMessage(
302                    "%s has more than one constructor annotated with @AssistedInject"
303                        + " that matches the parameters in method %s.  Unable to create AssistedInject factory.",
304                    implementation, method);
305            throw errors.toException();
306          } else {
307            matchingConstructor = constructor;
308          }
309        }
310      }
311    }
312
313    if(!anyAssistedInjectConstructors) {
314      // If none existed, use @Inject.
315      try {
316        return InjectionPoint.forConstructorOf(implementation);
317      } catch(ConfigurationException e) {
318        errors.merge(e.getErrorMessages());
319        throw errors.toException();
320      }
321    } else {
322      // Otherwise, use it or fail with a good error message.
323      if(matchingConstructor != null) {
324          // safe because we got the constructor from this implementation.
325          @SuppressWarnings("unchecked")
326          InjectionPoint ip = InjectionPoint.forConstructor(
327              (Constructor)matchingConstructor, implementation);
328          return ip;
329      } else {
330        errors.addMessage(
331            "%s has @AssistedInject constructors, but none of them match the"
332            + " parameters in method %s.  Unable to create AssistedInject factory.",
333            implementation, method);
334        throw errors.toException();
335      }
336    }
337  }
338
339  /**
340   * Matching logic for constructors annotated with AssistedInject.
341   * This returns true if and only if all @Assisted parameters in the
342   * constructor exactly match (in any order) all @Assisted parameters
343   * the method's parameter.
344   */
345  private boolean constructorHasMatchingParams(TypeLiteral<?> type,
346      Constructor<?> constructor, List<Key<?>> paramList, Errors errors)
347      throws ErrorsException {
348    List<TypeLiteral<?>> params = type.getParameterTypes(constructor);
349    Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
350    int p = 0;
351    List<Key<?>> constructorKeys = Lists.newArrayList();
352    for (TypeLiteral<?> param : params) {
353      Key<?> paramKey = getKey(param, constructor, paramAnnotations[p++],
354          errors);
355      constructorKeys.add(paramKey);
356    }
357    // Require that every key exist in the constructor to match up exactly.
358    for (Key<?> key : paramList) {
359      // If it didn't exist in the constructor set, we can't use it.
360      if (!constructorKeys.remove(key)) {
361        return false;
362      }
363    }
364    // If any keys remain and their annotation is Assisted, we can't use it.
365    for (Key<?> key : constructorKeys) {
366      if (key.getAnnotationType() == Assisted.class) {
367        return false;
368      }
369    }
370    // All @Assisted params match up to the method's parameters.
371    return true;
372  }
373
374  /**
375   * Returns true if the implementation & constructor are suitable for an
376   * optimized version of AssistedInject. The optimized version caches the
377   * binding & uses a ThreadLocal Provider, so can only be applied if the
378   * assisted bindings are immediately provided. This looks for hints that the
379   * values may be lazily retrieved, by looking for injections of Injector or a
380   * Provider for the assisted values.
381   */
382  private boolean validForOptimizedAssistedInject(InjectionPoint ctorPoint, TypeLiteral<?> implementation) {
383    if(ctorPoint != null) {
384      for(Dependency<?> dep : ctorPoint.getDependencies()) {
385        if(isInjectorOrAssistedProvider(dep)) {
386          return false;
387        }
388      }
389    }
390    if(!implementation.getRawType().isInterface()) {
391      for(InjectionPoint ip : InjectionPoint.forInstanceMethodsAndFields(implementation)) {
392        for(Dependency<?> dep : ip.getDependencies()) {
393          if(isInjectorOrAssistedProvider(dep)) {
394            return false;
395          }
396        }
397      }
398    }
399    return true;
400  }
401
402  /**
403   * Returns true if the dependency is for {@link Injector} or if the dependency
404   * is a {@link Provider} for a parameter that is {@literal @}{@link Assisted}.
405   */
406  private boolean isInjectorOrAssistedProvider(Dependency<?> dependency) {
407    Class annotationType = dependency.getKey().getAnnotationType();
408    if (annotationType != null && annotationType.equals(Assisted.class)) { // If it's assisted..
409      if (dependency.getKey().getTypeLiteral().getRawType().equals(Provider.class)) { // And a Provider...
410        return true;
411      }
412    } else if (dependency.getKey().getTypeLiteral().getRawType().equals(Injector.class)) { // If it's the Injector...
413      return true;
414    }
415    return false;
416  }
417
418  /**
419   * Returns a key similar to {@code key}, but with an {@literal @}Assisted binding annotation.
420   * This fails if another binding annotation is clobbered in the process. If the key already has
421   * the {@literal @}Assisted annotation, it is returned as-is to preserve any String value.
422   */
423  private <T> Key<T> assistKey(Method method, Key<T> key, Errors errors) throws ErrorsException {
424    if (key.getAnnotationType() == null) {
425      return Key.get(key.getTypeLiteral(), DEFAULT_ANNOTATION);
426    } else if (key.getAnnotationType() == Assisted.class) {
427      return key;
428    } else {
429      errors.withSource(method).addMessage(
430          "Only @Assisted is allowed for factory parameters, but found @%s",
431          key.getAnnotationType());
432      throw errors.toException();
433    }
434  }
435
436  /**
437   * At injector-creation time, we initialize the invocation handler. At this time we make sure
438   * all factory methods will be able to build the target types.
439   */
440  @Inject @Toolable
441  void initialize(Injector injector) {
442    if (this.injector != null) {
443      throw new ConfigurationException(ImmutableList.of(new Message(FactoryProvider2.class,
444          "Factories.create() factories may only be used in one Injector!")));
445    }
446
447    this.injector = injector;
448
449    for (Map.Entry<Method, AssistData> entry : assistDataByMethod.entrySet()) {
450      Method method = entry.getKey();
451      AssistData data = entry.getValue();
452      Object[] args;
453      if(!data.optimized) {
454        args = new Object[method.getParameterTypes().length];
455        Arrays.fill(args, "dummy object for validating Factories");
456      } else {
457        args = null; // won't be used -- instead will bind to data.providers.
458      }
459      getBindingFromNewInjector(method, args, data); // throws if the binding isn't properly configured
460    }
461  }
462
463  /**
464   * Creates a child injector that binds the args, and returns the binding for the method's result.
465   */
466  public Binding<?> getBindingFromNewInjector(final Method method, final Object[] args, final AssistData data) {
467    checkState(injector != null,
468        "Factories.create() factories cannot be used until they're initialized by Guice.");
469
470    final Key<?> returnType = data.returnType;
471
472    // We ignore any pre-existing binding annotation.
473    final Key<?> assistedReturnType = Key.get(returnType.getTypeLiteral(), Assisted.class);
474
475    Module assistedModule = new AbstractModule() {
476      @Override @SuppressWarnings("unchecked") // raw keys are necessary for the args array and return value
477      protected void configure() {
478        Binder binder = binder().withSource(method);
479
480        int p = 0;
481        if(!data.optimized) {
482          for (Key<?> paramKey : data.paramTypes) {
483            // Wrap in a Provider to cover null, and to prevent Guice from injecting the parameter
484            binder.bind((Key) paramKey).toProvider(Providers.of(args[p++]));
485          }
486        } else {
487          for (Key<?> paramKey : data.paramTypes) {
488            // Bind to our ThreadLocalProviders.
489            binder.bind((Key) paramKey).toProvider(data.providers.get(p++));
490          }
491        }
492
493        Constructor constructor = data.constructor;
494        // Constructor *should* always be non-null here,
495        // but if it isn't, we'll end up throwing a fairly good error
496        // message for the user.
497        if(constructor != null) {
498          TypeLiteral implementation = collector.getBindings().get(returnType);
499          if (implementation != null) {
500            binder.bind(assistedReturnType).toConstructor(constructor, implementation);
501          } else {
502            binder.bind(assistedReturnType).toConstructor(constructor, (TypeLiteral) returnType.getTypeLiteral());
503          }
504        }
505      }
506    };
507
508    Injector forCreate = injector.createChildInjector(assistedModule);
509    Binding binding = forCreate.getBinding(assistedReturnType);
510    // If we have providers cached in data, cache the binding for future optimizations.
511    if(data.optimized) {
512      data.cachedBinding = binding;
513    }
514    return binding;
515  }
516
517  /**
518   * When a factory method is invoked, we create a child injector that binds all parameters, then
519   * use that to get an instance of the return type.
520   */
521  public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
522    if (method.getDeclaringClass() == Object.class) {
523      return method.invoke(this, args);
524    }
525
526    AssistData data = assistDataByMethod.get(method);
527    Provider<?> provider;
528    if(data.cachedBinding != null) { // Try to get optimized form...
529      provider = data.cachedBinding.getProvider();
530    } else {
531      provider = getBindingFromNewInjector(method, args, data).getProvider();
532    }
533    try {
534      int p = 0;
535      for(ThreadLocalProvider tlp : data.providers) {
536        tlp.set(args[p++]);
537      }
538      return provider.get();
539    } catch (ProvisionException e) {
540      // if this is an exception declared by the factory method, throw it as-is
541      if (e.getErrorMessages().size() == 1) {
542        Message onlyError = getOnlyElement(e.getErrorMessages());
543        Throwable cause = onlyError.getCause();
544        if (cause != null && canRethrow(method, cause)) {
545          throw cause;
546        }
547      }
548      throw e;
549    } finally {
550      for(ThreadLocalProvider tlp : data.providers) {
551        tlp.remove();
552      }
553    }
554  }
555
556  @Override public String toString() {
557    return factory.getClass().getInterfaces()[0].getName();
558  }
559
560  @Override public boolean equals(Object o) {
561    return o == this || o == factory;
562  }
563
564  /** Returns true if {@code thrown} can be thrown by {@code invoked} without wrapping. */
565  static boolean canRethrow(Method invoked, Throwable thrown) {
566    if (thrown instanceof Error || thrown instanceof RuntimeException) {
567      return true;
568    }
569
570    for (Class<?> declared : invoked.getExceptionTypes()) {
571      if (declared.isInstance(thrown)) {
572        return true;
573      }
574    }
575
576    return false;
577  }
578
579  // not <T> because we'll never know and this is easier than suppressing warnings.
580  private static class ThreadLocalProvider extends ThreadLocal<Object> implements Provider<Object> {
581    @Override
582    protected Object initialValue() {
583      throw new IllegalStateException(
584          "Cannot use optimized @Assisted provider outside the scope of the constructor."
585              + " (This should never happen.  If it does, please report it.)");
586    }
587  }
588}
589