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