FactoryProvider2.java revision 88f9a14dc9fa9f6704f8a86e376b7f218e492f98
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 keys.add(assistKey(method, paramKey, errors)); 183 } 184 ImmutableList<Key<?>> immutableParamList = ImmutableList.copyOf(keys); 185 186 // try to match up the method to the constructor 187 TypeLiteral<?> implementation = collector.getBindings().get(returnType); 188 if(implementation == null) { 189 implementation = returnType.getTypeLiteral(); 190 } 191 InjectionPoint ctorInjectionPoint; 192 try { 193 ctorInjectionPoint = 194 findMatchingConstructorInjectionPoint(method, returnType, implementation, immutableParamList); 195 } catch(ErrorsException ee) { 196 errors.merge(ee.getErrors()); 197 continue; 198 } 199 200 Constructor<?> constructor = (Constructor)ctorInjectionPoint.getMember(); 201 List<ThreadLocalProvider> providers = Collections.emptyList(); 202 boolean optimized = false; 203 // Now go through all dependencies of the implementation and see if it is OK to 204 // use an optimized form of assistedinject2. The optimized form requires that 205 // all injections directly inject the object itself (and not a Provider of the object, 206 // or an Injector), because it caches a single child injector and mutates the Provider 207 // of the arguments in a ThreadLocal. 208 if(validForOptimizedAssistedInject(ctorInjectionPoint, implementation)) { 209 ImmutableList.Builder<ThreadLocalProvider> providerListBuilder = ImmutableList.builder(); 210 for(int i = 0; i < params.size(); i++) { 211 providerListBuilder.add(new ThreadLocalProvider()); 212 } 213 providers = providerListBuilder.build(); 214 optimized = true; 215 } 216 assistDataBuilder.put(method, new AssistData(constructor, returnType, immutableParamList, optimized, providers)); 217 } 218 219 // If we generated any errors (from finding matching constructors, for instance), throw an exception. 220 if(errors.hasErrors()) { 221 throw errors.toException(); 222 } 223 224 assistDataByMethod = assistDataBuilder.build(); 225 } catch (ErrorsException e) { 226 throw new ConfigurationException(e.getErrors().getMessages()); 227 } 228 229 factory = factoryRawType.cast(Proxy.newProxyInstance(BytecodeGen.getClassLoader(factoryRawType), 230 new Class[] { factoryRawType }, this)); 231 } 232 233 public F get() { 234 return factory; 235 } 236 237 /** 238 * Returns true if the ConfigurationException is due to an error of TypeLiteral not being fully 239 * specified. 240 */ 241 private boolean isTypeNotSpecified(TypeLiteral typeLiteral, ConfigurationException ce) { 242 Collection<Message> messages = ce.getErrorMessages(); 243 if (messages.size() == 1) { 244 Message msg = Iterables.getOnlyElement( 245 new Errors().keyNotFullySpecified(typeLiteral).getMessages()); 246 return msg.getMessage().equals(Iterables.getOnlyElement(messages).getMessage()); 247 } else { 248 return false; 249 } 250 } 251 252 /** 253 * Finds a constructor suitable for the method. If the implementation contained any constructors 254 * marked with {@link AssistedInject}, this requires all {@link Assisted} parameters to exactly 255 * match the parameters (in any order) listed in the method. Otherwise, if no 256 * {@link AssistedInject} constructors exist, this will default to looking for an 257 * {@literal @}{@link Inject} constructor. 258 */ 259 private InjectionPoint findMatchingConstructorInjectionPoint( 260 Method method, Key<?> returnType, TypeLiteral<?> implementation, List<Key<?>> paramList) 261 throws ErrorsException { 262 Errors errors = new Errors(method); 263 if(returnType.getTypeLiteral().equals(implementation)) { 264 errors = errors.withSource(implementation); 265 } else { 266 errors = errors.withSource(returnType).withSource(implementation); 267 } 268 269 Class<?> rawType = implementation.getRawType(); 270 if (Modifier.isInterface(rawType.getModifiers())) { 271 errors.addMessage( 272 "%s is an interface, not a concrete class. Unable to create AssistedInject factory.", 273 implementation); 274 throw errors.toException(); 275 } else if (Modifier.isAbstract(rawType.getModifiers())) { 276 errors.addMessage( 277 "%s is abstract, not a concrete class. Unable to create AssistedInject factory.", 278 implementation); 279 throw errors.toException(); 280 } else if (Classes.isInnerClass(rawType)) { 281 errors.cannotInjectInnerClass(rawType); 282 throw errors.toException(); 283 } 284 285 Constructor<?> matchingConstructor = null; 286 boolean anyAssistedInjectConstructors = false; 287 // Look for AssistedInject constructors... 288 for (Constructor<?> constructor : rawType.getDeclaredConstructors()) { 289 if (constructor.isAnnotationPresent(AssistedInject.class)) { 290 anyAssistedInjectConstructors = true; 291 if (constructorHasMatchingParams(implementation, constructor, paramList, errors)) { 292 if (matchingConstructor != null) { 293 errors 294 .addMessage( 295 "%s has more than one constructor annotated with @AssistedInject" 296 + " that matches the parameters in method %s. Unable to create AssistedInject factory.", 297 implementation, method); 298 throw errors.toException(); 299 } else { 300 matchingConstructor = constructor; 301 } 302 } 303 } 304 } 305 306 if(!anyAssistedInjectConstructors) { 307 // If none existed, use @Inject. 308 try { 309 return InjectionPoint.forConstructorOf(implementation); 310 } catch(ConfigurationException e) { 311 errors.merge(e.getErrorMessages()); 312 throw errors.toException(); 313 } 314 } else { 315 // Otherwise, use it or fail with a good error message. 316 if(matchingConstructor != null) { 317 // safe because we got the constructor from this implementation. 318 @SuppressWarnings("unchecked") 319 InjectionPoint ip = InjectionPoint.forConstructor( 320 (Constructor)matchingConstructor, implementation); 321 return ip; 322 } else { 323 errors.addMessage( 324 "%s has @AssistedInject constructors, but none of them match the" 325 + " parameters in method %s. Unable to create AssistedInject factory.", 326 implementation, method); 327 throw errors.toException(); 328 } 329 } 330 } 331 332 /** 333 * Matching logic for constructors annotated with AssistedInject. 334 * This returns true if and only if all @Assisted parameters in the 335 * constructor exactly match (in any order) all @Assisted parameters 336 * the method's parameter. 337 */ 338 private boolean constructorHasMatchingParams(TypeLiteral<?> type, 339 Constructor<?> constructor, List<Key<?>> paramList, Errors errors) 340 throws ErrorsException { 341 List<TypeLiteral<?>> params = type.getParameterTypes(constructor); 342 Annotation[][] paramAnnotations = constructor.getParameterAnnotations(); 343 int p = 0; 344 List<Key<?>> constructorKeys = Lists.newArrayList(); 345 for (TypeLiteral<?> param : params) { 346 Key<?> paramKey = getKey(param, constructor, paramAnnotations[p++], 347 errors); 348 constructorKeys.add(paramKey); 349 } 350 // Require that every key exist in the constructor to match up exactly. 351 for (Key<?> key : paramList) { 352 // If it didn't exist in the constructor set, we can't use it. 353 if (!constructorKeys.remove(key)) { 354 return false; 355 } 356 } 357 // If any keys remain and their annotation is Assisted, we can't use it. 358 for (Key<?> key : constructorKeys) { 359 if (key.getAnnotationType() == Assisted.class) { 360 return false; 361 } 362 } 363 // All @Assisted params match up to the method's parameters. 364 return true; 365 } 366 367 /** 368 * Returns true if the implementation & constructor are suitable for an 369 * optimized version of AssistedInject. The optimized version caches the 370 * binding & uses a ThreadLocal Provider, so can only be applied if the 371 * assisted bindings are immediately provided. This looks for hints that the 372 * values may be lazily retrieved, by looking for injections of Injector or a 373 * Provider for the assisted values. 374 */ 375 private boolean validForOptimizedAssistedInject(InjectionPoint ctorPoint, TypeLiteral<?> implementation) { 376 if(ctorPoint != null) { 377 for(Dependency<?> dep : ctorPoint.getDependencies()) { 378 if(isInjectorOrAssistedProvider(dep)) { 379 return false; 380 } 381 } 382 } 383 if(!implementation.getRawType().isInterface()) { 384 for(InjectionPoint ip : InjectionPoint.forInstanceMethodsAndFields(implementation)) { 385 for(Dependency<?> dep : ip.getDependencies()) { 386 if(isInjectorOrAssistedProvider(dep)) { 387 return false; 388 } 389 } 390 } 391 } 392 return true; 393 } 394 395 /** 396 * Returns true if the dependency is for {@link Injector} or if the dependency 397 * is a {@link Provider} for a parameter that is {@literal @}{@link Assisted}. 398 */ 399 private boolean isInjectorOrAssistedProvider(Dependency<?> dependency) { 400 Class annotationType = dependency.getKey().getAnnotationType(); 401 if (annotationType != null && annotationType.equals(Assisted.class)) { // If it's assisted.. 402 if (dependency.getKey().getTypeLiteral().getRawType().equals(Provider.class)) { // And a Provider... 403 return true; 404 } 405 } else if (dependency.getKey().getTypeLiteral().getRawType().equals(Injector.class)) { // If it's the Injector... 406 return true; 407 } 408 return false; 409 } 410 411 /** 412 * Returns a key similar to {@code key}, but with an {@literal @}Assisted binding annotation. 413 * This fails if another binding annotation is clobbered in the process. If the key already has 414 * the {@literal @}Assisted annotation, it is returned as-is to preserve any String value. 415 */ 416 private <T> Key<T> assistKey(Method method, Key<T> key, Errors errors) throws ErrorsException { 417 if (key.getAnnotationType() == null) { 418 return Key.get(key.getTypeLiteral(), DEFAULT_ANNOTATION); 419 } else if (key.getAnnotationType() == Assisted.class) { 420 return key; 421 } else { 422 errors.withSource(method).addMessage( 423 "Only @Assisted is allowed for factory parameters, but found @%s", 424 key.getAnnotationType()); 425 throw errors.toException(); 426 } 427 } 428 429 /** 430 * At injector-creation time, we initialize the invocation handler. At this time we make sure 431 * all factory methods will be able to build the target types. 432 */ 433 @Inject @Toolable 434 void initialize(Injector injector) { 435 if (this.injector != null) { 436 throw new ConfigurationException(ImmutableList.of(new Message(FactoryProvider2.class, 437 "Factories.create() factories may only be used in one Injector!"))); 438 } 439 440 this.injector = injector; 441 442 for (Map.Entry<Method, AssistData> entry : assistDataByMethod.entrySet()) { 443 Method method = entry.getKey(); 444 AssistData data = entry.getValue(); 445 Object[] args; 446 if(!data.optimized) { 447 args = new Object[method.getParameterTypes().length]; 448 Arrays.fill(args, "dummy object for validating Factories"); 449 } else { 450 args = null; // won't be used -- instead will bind to data.providers. 451 } 452 getBindingFromNewInjector(method, args, data); // throws if the binding isn't properly configured 453 } 454 } 455 456 /** 457 * Creates a child injector that binds the args, and returns the binding for the method's result. 458 */ 459 public Binding<?> getBindingFromNewInjector(final Method method, final Object[] args, final AssistData data) { 460 checkState(injector != null, 461 "Factories.create() factories cannot be used until they're initialized by Guice."); 462 463 final Key<?> returnType = data.returnType; 464 465 // We ignore any pre-existing binding annotation. 466 final Key<?> assistedReturnType = Key.get(returnType.getTypeLiteral(), Assisted.class); 467 468 Module assistedModule = new AbstractModule() { 469 @SuppressWarnings("unchecked") // raw keys are necessary for the args array and return value 470 protected void configure() { 471 Binder binder = binder().withSource(method); 472 473 int p = 0; 474 if(!data.optimized) { 475 for (Key<?> paramKey : data.paramTypes) { 476 // Wrap in a Provider to cover null, and to prevent Guice from injecting the parameter 477 binder.bind((Key) paramKey).toProvider(Providers.of(args[p++])); 478 } 479 } else { 480 for (Key<?> paramKey : data.paramTypes) { 481 // Bind to our ThreadLocalProviders. 482 binder.bind((Key) paramKey).toProvider(data.providers.get(p++)); 483 } 484 } 485 486 Constructor constructor = data.constructor; 487 // Constructor *should* always be non-null here, 488 // but if it isn't, we'll end up throwing a fairly good error 489 // message for the user. 490 if(constructor != null) { 491 TypeLiteral implementation = collector.getBindings().get(returnType); 492 if (implementation != null) { 493 binder.bind(assistedReturnType).toConstructor(constructor, implementation); 494 } else { 495 binder.bind(assistedReturnType).toConstructor(constructor, (TypeLiteral) returnType.getTypeLiteral()); 496 } 497 } 498 } 499 }; 500 501 Injector forCreate = injector.createChildInjector(assistedModule); 502 Binding binding = forCreate.getBinding(assistedReturnType); 503 // If we have providers cached in data, cache the binding for future optimizations. 504 if(data.optimized) { 505 data.cachedBinding = binding; 506 } 507 return binding; 508 } 509 510 /** 511 * When a factory method is invoked, we create a child injector that binds all parameters, then 512 * use that to get an instance of the return type. 513 */ 514 public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable { 515 if (method.getDeclaringClass() == Object.class) { 516 return method.invoke(this, args); 517 } 518 519 AssistData data = assistDataByMethod.get(method); 520 Provider<?> provider; 521 if(data.cachedBinding != null) { // Try to get optimized form... 522 provider = data.cachedBinding.getProvider(); 523 } else { 524 provider = getBindingFromNewInjector(method, args, data).getProvider(); 525 } 526 try { 527 int p = 0; 528 for(ThreadLocalProvider tlp : data.providers) { 529 tlp.set(args[p++]); 530 } 531 return provider.get(); 532 } catch (ProvisionException e) { 533 // if this is an exception declared by the factory method, throw it as-is 534 if (e.getErrorMessages().size() == 1) { 535 Message onlyError = getOnlyElement(e.getErrorMessages()); 536 Throwable cause = onlyError.getCause(); 537 if (cause != null && canRethrow(method, cause)) { 538 throw cause; 539 } 540 } 541 throw e; 542 } finally { 543 for(ThreadLocalProvider tlp : data.providers) { 544 tlp.remove(); 545 } 546 } 547 } 548 549 @Override public String toString() { 550 return factory.getClass().getInterfaces()[0].getName(); 551 } 552 553 @Override public boolean equals(Object o) { 554 return o == this || o == factory; 555 } 556 557 /** Returns true if {@code thrown} can be thrown by {@code invoked} without wrapping. */ 558 static boolean canRethrow(Method invoked, Throwable thrown) { 559 if (thrown instanceof Error || thrown instanceof RuntimeException) { 560 return true; 561 } 562 563 for (Class<?> declared : invoked.getExceptionTypes()) { 564 if (declared.isInstance(thrown)) { 565 return true; 566 } 567 } 568 569 return false; 570 } 571 572 // not <T> because we'll never know and this is easier than suppressing warnings. 573 private static class ThreadLocalProvider extends ThreadLocal<Object> implements Provider<Object> { 574 @Override 575 protected Object initialValue() { 576 throw new IllegalStateException( 577 "Cannot use optimized @Assisted provider outside the scope of the constructor." 578 + " (This should never happen. If it does, please report it.)"); 579 } 580 } 581} 582