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