FactoryProvider2.java revision 53664a7f17492bd0c3c4728df61679147907dd18
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 static com.google.inject.internal.Iterables.getOnlyElement;
36import com.google.inject.internal.Lists;
37import static com.google.inject.internal.Preconditions.checkState;
38import com.google.inject.spi.Message;
39import com.google.inject.util.Providers;
40import java.lang.annotation.Annotation;
41import java.lang.reflect.InvocationHandler;
42import java.lang.reflect.Method;
43import java.lang.reflect.Proxy;
44import java.lang.reflect.Type;
45import java.util.Arrays;
46import java.util.List;
47
48/**
49 * The newer implementation of factory provider. This implementation uses a child injector to
50 * create values.
51 *
52 * @author jessewilson@google.com (Jesse Wilson)
53 * @author dtm@google.com (Daniel Martin)
54 */
55final class FactoryProvider2<F> implements InvocationHandler, Provider<F> {
56
57  /** if a factory method parameter isn't annotated, it gets this annotation. */
58  static final Assisted DEFAULT_ANNOTATION = new Assisted() {
59    public String value() {
60      return "";
61    }
62
63    public Class<? extends Annotation> annotationType() {
64      return Assisted.class;
65    }
66
67    @Override public boolean equals(Object o) {
68      return o instanceof Assisted
69          && ((Assisted) o).value().equals("");
70    }
71
72    @Override public int hashCode() {
73      return 127 * "value".hashCode() ^ "".hashCode();
74    }
75
76    @Override public String toString() {
77      return "@" + Assisted.class.getName() + "(value=)";
78    }
79  };
80
81  /** the produced type, or null if all methods return concrete types */
82  private final Key<?> producedType;
83  private final ImmutableMap<Method, Key<?>> returnTypesByMethod;
84  private final ImmutableMap<Method, ImmutableList<Key<?>>> paramTypes;
85
86  /** the hosting injector, or null if we haven't been initialized yet */
87  private Injector injector;
88
89  /** the factory interface, implemented and provided */
90  private final F factory;
91
92  /**
93   * @param factoryType a Java interface that defines one or more create methods.
94   * @param producedType a concrete type that is assignable to the return types of all factory
95   *      methods.
96   */
97  FactoryProvider2(Class<F> factoryType, Key<?> producedType) {
98    this.producedType = producedType;
99
100    Errors errors = new Errors();
101    try {
102      ImmutableMap.Builder<Method, Key<?>> returnTypesBuilder = ImmutableMap.builder();
103      ImmutableMap.Builder<Method, ImmutableList<Key<?>>> paramTypesBuilder
104          = ImmutableMap.builder();
105      // TODO: also grab methods from superinterfaces
106      for (Method method : factoryType.getMethods()) {
107        Key<?> returnType = getKey(TypeLiteral.get(method.getGenericReturnType()),
108            method, method.getAnnotations(), errors);
109        returnTypesBuilder.put(method, returnType);
110        Type[] params = method.getGenericParameterTypes();
111        Annotation[][] paramAnnotations = method.getParameterAnnotations();
112        int p = 0;
113        List<Key<?>> keys = Lists.newArrayList();
114        for (Type param : params) {
115          Key<?> paramKey = getKey(TypeLiteral.get(param), method, paramAnnotations[p++], errors);
116          keys.add(assistKey(method, paramKey, errors));
117        }
118        paramTypesBuilder.put(method, ImmutableList.copyOf(keys));
119      }
120      returnTypesByMethod = returnTypesBuilder.build();
121      paramTypes = paramTypesBuilder.build();
122    } catch (ErrorsException e) {
123      throw new ConfigurationException(e.getErrors().getMessages());
124    }
125
126    factory = factoryType.cast(Proxy.newProxyInstance(factoryType.getClassLoader(),
127        new Class[] { factoryType }, this));
128  }
129
130  public F get() {
131    return factory;
132  }
133
134  /**
135   * Returns a key similar to {@code key}, but with an {@literal @}Assisted binding annotation.
136   * This fails if another binding annotation is clobbered in the process. If the key already has
137   * the {@literal @}Assisted annotation, it is returned as-is to preserve any String value.
138   */
139  private <T> Key<T> assistKey(Method method, Key<T> key, Errors errors) throws ErrorsException {
140    if (key.getAnnotationType() == null) {
141      return Key.get(key.getTypeLiteral(), DEFAULT_ANNOTATION);
142    } else if (key.getAnnotationType() == Assisted.class) {
143      return key;
144    } else {
145      errors.withSource(method).addMessage(
146          "Only @Assisted is allowed for factory parameters, but found @%s",
147          key.getAnnotationType());
148      throw errors.toException();
149    }
150  }
151
152  /**
153   * At injector-creation time, we initialize the invocation handler. At this time we make sure
154   * all factory methods will be able to build the target types.
155   */
156  @Inject
157  void initialize(Injector injector) {
158    if (this.injector != null) {
159      throw new ConfigurationException(ImmutableList.of(new Message(FactoryProvider2.class,
160          "Factories.create() factories may only be used in one Injector!")));
161    }
162
163    this.injector = injector;
164
165    for (Method method : returnTypesByMethod.keySet()) {
166      Object[] args = new Object[method.getParameterTypes().length];
167      Arrays.fill(args, "dummy object for validating Factories");
168      getBindingFromNewInjector(method, args); // throws if the binding isn't properly configured
169    }
170  }
171
172  /**
173   * Creates a child injector that binds the args, and returns the binding for the method's result.
174   */
175  public Binding<?> getBindingFromNewInjector(final Method method, final Object[] args) {
176    checkState(injector != null,
177        "Factories.create() factories cannot be used until they're initialized by Guice.");
178
179    final Key<?> returnType = returnTypesByMethod.get(method);
180
181    Module assistedModule = new AbstractModule() {
182      @SuppressWarnings("unchecked") // raw keys are necessary for the args array and return value
183      protected void configure() {
184        Binder binder = binder().withSource(method);
185
186        int p = 0;
187        for (Key<?> paramKey : paramTypes.get(method)) {
188          // Wrap in a Provider to cover null, and to prevent Guice from injecting the parameter
189          binder.bind((Key) paramKey).toProvider(Providers.of(args[p++]));
190        }
191
192        if (producedType != null && !returnType.equals(producedType)) {
193          binder.bind(returnType).to((Key) producedType);
194        } else {
195          binder.bind(returnType);
196        }
197      }
198    };
199
200    Injector forCreate = injector.createChildInjector(assistedModule);
201    return forCreate.getBinding(returnType);
202  }
203
204  /**
205   * When a factory method is invoked, we create a child injector that binds all parameters, then
206   * use that to get an instance of the return type.
207   */
208  public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
209    if (method.getDeclaringClass() == Object.class) {
210      return method.invoke(this, args);
211    }
212
213    Provider<?> provider = getBindingFromNewInjector(method, args).getProvider();
214    try {
215      return provider.get();
216    } catch (ProvisionException e) {
217      // if this is an exception declared by the factory method, throw it as-is
218      if (e.getErrorMessages().size() == 1) {
219        Message onlyError = getOnlyElement(e.getErrorMessages());
220        Throwable cause = onlyError.getCause();
221        if (cause != null && canRethrow(method, cause)) {
222          throw cause;
223        }
224      }
225      throw e;
226    }
227  }
228
229  @Override public String toString() {
230    return factory.getClass().getInterfaces()[0].getName()
231        + " for " + producedType.getTypeLiteral();
232  }
233
234  @Override public boolean equals(Object o) {
235    return o == this || o == factory;
236  }
237
238  /** Returns true if {@code thrown} can be thrown by {@code invoked} without wrapping. */
239  static boolean canRethrow(Method invoked, Throwable thrown) {
240    if (thrown instanceof Error || thrown instanceof RuntimeException) {
241      return true;
242    }
243
244    for (Class<?> declared : invoked.getExceptionTypes()) {
245      if (declared.isInstance(thrown)) {
246        return true;
247      }
248    }
249
250    return false;
251  }
252}
253