1 /* 2 * Copyright (C) 2007 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 17 package com.google.inject.assistedinject; 18 19 import static com.google.inject.internal.Annotations.getKey; 20 21 import com.google.common.base.Objects; 22 import com.google.common.collect.ImmutableMap; 23 import com.google.common.collect.ImmutableSet; 24 import com.google.common.collect.Lists; 25 import com.google.common.collect.Maps; 26 import com.google.inject.ConfigurationException; 27 import com.google.inject.Inject; 28 import com.google.inject.Injector; 29 import com.google.inject.Key; 30 import com.google.inject.Provider; 31 import com.google.inject.TypeLiteral; 32 import com.google.inject.internal.BytecodeGen; 33 import com.google.inject.internal.Errors; 34 import com.google.inject.internal.ErrorsException; 35 import com.google.inject.spi.Dependency; 36 import com.google.inject.spi.HasDependencies; 37 import com.google.inject.spi.Message; 38 import java.lang.annotation.Annotation; 39 import java.lang.reflect.Constructor; 40 import java.lang.reflect.InvocationHandler; 41 import java.lang.reflect.Method; 42 import java.lang.reflect.Proxy; 43 import java.lang.reflect.Type; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.Set; 47 48 /** 49 * <strong>Obsolete.</strong> Prefer {@link FactoryModuleBuilder} for its more concise API and 50 * additional capability. 51 * 52 * <p>Provides a factory that combines the caller's arguments with injector-supplied values to 53 * construct objects. 54 * 55 * <h3>Defining a factory</h3> 56 * 57 * Create an interface whose methods return the constructed type, or any of its supertypes. The 58 * method's parameters are the arguments required to build the constructed type. 59 * 60 * <pre>public interface PaymentFactory { 61 * Payment create(Date startDate, Money amount); 62 * }</pre> 63 * 64 * You can name your factory methods whatever you like, such as <i>create</i>, <i>createPayment</i> 65 * or <i>newPayment</i>. 66 * 67 * <h3>Creating a type that accepts factory parameters</h3> 68 * 69 * {@code constructedType} is a concrete class with an {@literal @}{@link Inject}-annotated 70 * constructor. In addition to injector-supplied parameters, the constructor should have parameters 71 * that match each of the factory method's parameters. Each factory-supplied parameter requires an 72 * {@literal @}{@link Assisted} annotation. This serves to document that the parameter is not bound 73 * by your application's modules. 74 * 75 * <pre>public class RealPayment implements Payment { 76 * {@literal @}Inject 77 * public RealPayment( 78 * CreditService creditService, 79 * AuthService authService, 80 * <strong>{@literal @}Assisted Date startDate</strong>, 81 * <strong>{@literal @}Assisted Money amount</strong>) { 82 * ... 83 * } 84 * }</pre> 85 * 86 * Any parameter that permits a null value should also be annotated {@code @Nullable}. 87 * 88 * <h3>Configuring factories</h3> 89 * 90 * In your {@link com.google.inject.Module module}, bind the factory interface to the returned 91 * factory: 92 * 93 * <pre>bind(PaymentFactory.class).toProvider( 94 * FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));</pre> 95 * 96 * As a side-effect of this binding, Guice will inject the factory to initialize it for use. The 97 * factory cannot be used until the injector has been initialized. 98 * 99 * <h3>Using the factory</h3> 100 * 101 * Inject your factory into your application classes. When you use the factory, your arguments will 102 * be combined with values from the injector to construct an instance. 103 * 104 * <pre>public class PaymentAction { 105 * {@literal @}Inject private PaymentFactory paymentFactory; 106 * 107 * public void doPayment(Money amount) { 108 * Payment payment = paymentFactory.create(new Date(), amount); 109 * payment.apply(); 110 * } 111 * }</pre> 112 * 113 * <h3>Making parameter types distinct</h3> 114 * 115 * The types of the factory method's parameters must be distinct. To use multiple parameters of the 116 * same type, use a named {@literal @}{@link Assisted} annotation to disambiguate the parameters. 117 * The names must be applied to the factory method's parameters: 118 * 119 * <pre>public interface PaymentFactory { 120 * Payment create( 121 * <strong>{@literal @}Assisted("startDate")</strong> Date startDate, 122 * <strong>{@literal @}Assisted("dueDate")</strong> Date dueDate, 123 * Money amount); 124 * } </pre> 125 * 126 * ...and to the concrete type's constructor parameters: 127 * 128 * <pre>public class RealPayment implements Payment { 129 * {@literal @}Inject 130 * public RealPayment( 131 * CreditService creditService, 132 * AuthService authService, 133 * <strong>{@literal @}Assisted("startDate")</strong> Date startDate, 134 * <strong>{@literal @}Assisted("dueDate")</strong> Date dueDate, 135 * <strong>{@literal @}Assisted</strong> Money amount) { 136 * ... 137 * } 138 * }</pre> 139 * 140 * <h3>Values are created by Guice</h3> 141 * 142 * Returned factories use child injectors to create values. The values are eligible for method 143 * interception. In addition, {@literal @}{@literal Inject} members will be injected before they are 144 * returned. 145 * 146 * <h3>Backwards compatibility using {@literal @}AssistedInject</h3> 147 * 148 * Instead of the {@literal @}Inject annotation, you may annotate the constructed classes with 149 * {@literal @}{@link AssistedInject}. This triggers a limited backwards-compatability mode. 150 * 151 * <p>Instead of matching factory method arguments to constructor parameters using their names, the 152 * <strong>parameters are matched by their order</strong>. The first factory method argument is used 153 * for the first {@literal @}Assisted constructor parameter, etc.. Annotation names have no effect. 154 * 155 * <p>Returned values are <strong>not created by Guice</strong>. These types are not eligible for 156 * method interception. They do receive post-construction member injection. 157 * 158 * @param <F> The factory interface 159 * @author jmourits@google.com (Jerome Mourits) 160 * @author jessewilson@google.com (Jesse Wilson) 161 * @author dtm@google.com (Daniel Martin) 162 * @deprecated use {@link FactoryModuleBuilder} instead. 163 */ 164 @Deprecated 165 public class FactoryProvider<F> implements Provider<F>, HasDependencies { 166 167 /* 168 * This class implements the old @AssistedInject implementation that manually matches constructors 169 * to factory methods. The new child injector implementation lives in FactoryProvider2. 170 */ 171 172 private Injector injector; 173 174 private final TypeLiteral<F> factoryType; 175 private final TypeLiteral<?> implementationType; 176 private final Map<Method, AssistedConstructor<?>> factoryMethodToConstructor; 177 newFactory(Class<F> factoryType, Class<?> implementationType)178 public static <F> Provider<F> newFactory(Class<F> factoryType, Class<?> implementationType) { 179 return newFactory(TypeLiteral.get(factoryType), TypeLiteral.get(implementationType)); 180 } 181 newFactory( TypeLiteral<F> factoryType, TypeLiteral<?> implementationType)182 public static <F> Provider<F> newFactory( 183 TypeLiteral<F> factoryType, TypeLiteral<?> implementationType) { 184 Map<Method, AssistedConstructor<?>> factoryMethodToConstructor = 185 createMethodMapping(factoryType, implementationType); 186 187 if (!factoryMethodToConstructor.isEmpty()) { 188 return new FactoryProvider<F>(factoryType, implementationType, factoryMethodToConstructor); 189 } else { 190 BindingCollector collector = new BindingCollector(); 191 192 // Preserving backwards-compatibility: Map all return types in a factory 193 // interface to the passed implementation type. 194 Errors errors = new Errors(); 195 Key<?> implementationKey = Key.get(implementationType); 196 197 try { 198 for (Method method : factoryType.getRawType().getMethods()) { 199 Key<?> returnType = 200 getKey(factoryType.getReturnType(method), method, method.getAnnotations(), errors); 201 if (!implementationKey.equals(returnType)) { 202 collector.addBinding(returnType, implementationType); 203 } 204 } 205 } catch (ErrorsException e) { 206 throw new ConfigurationException(e.getErrors().getMessages()); 207 } 208 209 return new FactoryProvider2<F>(Key.get(factoryType), collector); 210 } 211 } 212 FactoryProvider( TypeLiteral<F> factoryType, TypeLiteral<?> implementationType, Map<Method, AssistedConstructor<?>> factoryMethodToConstructor)213 private FactoryProvider( 214 TypeLiteral<F> factoryType, 215 TypeLiteral<?> implementationType, 216 Map<Method, AssistedConstructor<?>> factoryMethodToConstructor) { 217 this.factoryType = factoryType; 218 this.implementationType = implementationType; 219 this.factoryMethodToConstructor = factoryMethodToConstructor; 220 checkDeclaredExceptionsMatch(); 221 } 222 223 @Inject setInjectorAndCheckUnboundParametersAreInjectable(Injector injector)224 void setInjectorAndCheckUnboundParametersAreInjectable(Injector injector) { 225 this.injector = injector; 226 for (AssistedConstructor<?> c : factoryMethodToConstructor.values()) { 227 for (Parameter p : c.getAllParameters()) { 228 if (!p.isProvidedByFactory() && !paramCanBeInjected(p, injector)) { 229 // this is lame - we're not using the proper mechanism to add an 230 // error to the injector. Throughout this class we throw exceptions 231 // to add errors, which isn't really the best way in Guice 232 throw newConfigurationException( 233 "Parameter of type '%s' is not injectable or annotated " 234 + "with @Assisted for Constructor '%s'", 235 p, c); 236 } 237 } 238 } 239 } 240 checkDeclaredExceptionsMatch()241 private void checkDeclaredExceptionsMatch() { 242 for (Map.Entry<Method, AssistedConstructor<?>> entry : factoryMethodToConstructor.entrySet()) { 243 for (Class<?> constructorException : entry.getValue().getDeclaredExceptions()) { 244 if (!isConstructorExceptionCompatibleWithFactoryExeception( 245 constructorException, entry.getKey().getExceptionTypes())) { 246 throw newConfigurationException( 247 "Constructor %s declares an exception, but no compatible " 248 + "exception is thrown by the factory method %s", 249 entry.getValue(), entry.getKey()); 250 } 251 } 252 } 253 } 254 isConstructorExceptionCompatibleWithFactoryExeception( Class<?> constructorException, Class<?>[] factoryExceptions)255 private boolean isConstructorExceptionCompatibleWithFactoryExeception( 256 Class<?> constructorException, Class<?>[] factoryExceptions) { 257 for (Class<?> factoryException : factoryExceptions) { 258 if (factoryException.isAssignableFrom(constructorException)) { 259 return true; 260 } 261 } 262 return false; 263 } 264 paramCanBeInjected(Parameter parameter, Injector injector)265 private boolean paramCanBeInjected(Parameter parameter, Injector injector) { 266 return parameter.isBound(injector); 267 } 268 createMethodMapping( TypeLiteral<?> factoryType, TypeLiteral<?> implementationType)269 private static Map<Method, AssistedConstructor<?>> createMethodMapping( 270 TypeLiteral<?> factoryType, TypeLiteral<?> implementationType) { 271 List<AssistedConstructor<?>> constructors = Lists.newArrayList(); 272 273 for (Constructor<?> constructor : implementationType.getRawType().getDeclaredConstructors()) { 274 if (constructor.isAnnotationPresent(AssistedInject.class)) { 275 AssistedConstructor<?> assistedConstructor = 276 AssistedConstructor.create( 277 constructor, implementationType.getParameterTypes(constructor)); 278 constructors.add(assistedConstructor); 279 } 280 } 281 282 if (constructors.isEmpty()) { 283 return ImmutableMap.of(); 284 } 285 286 Method[] factoryMethods = factoryType.getRawType().getMethods(); 287 288 if (constructors.size() != factoryMethods.length) { 289 throw newConfigurationException( 290 "Constructor mismatch: %s has %s @AssistedInject " 291 + "constructors, factory %s has %s creation methods", 292 implementationType, constructors.size(), factoryType, factoryMethods.length); 293 } 294 295 Map<ParameterListKey, AssistedConstructor<?>> paramsToConstructor = Maps.newHashMap(); 296 297 for (AssistedConstructor<?> c : constructors) { 298 if (paramsToConstructor.containsKey(c.getAssistedParameters())) { 299 throw new RuntimeException("Duplicate constructor, " + c); 300 } 301 paramsToConstructor.put(c.getAssistedParameters(), c); 302 } 303 304 Map<Method, AssistedConstructor<?>> result = Maps.newHashMap(); 305 for (Method method : factoryMethods) { 306 if (!method.getReturnType().isAssignableFrom(implementationType.getRawType())) { 307 throw newConfigurationException( 308 "Return type of method %s is not assignable from %s", method, implementationType); 309 } 310 311 List<Type> parameterTypes = Lists.newArrayList(); 312 for (TypeLiteral<?> parameterType : factoryType.getParameterTypes(method)) { 313 parameterTypes.add(parameterType.getType()); 314 } 315 ParameterListKey methodParams = new ParameterListKey(parameterTypes); 316 317 if (!paramsToConstructor.containsKey(methodParams)) { 318 throw newConfigurationException( 319 "%s has no @AssistInject constructor that takes the " 320 + "@Assisted parameters %s in that order. @AssistInject constructors are %s", 321 implementationType, methodParams, paramsToConstructor.values()); 322 } 323 324 method.getParameterAnnotations(); 325 for (Annotation[] parameterAnnotations : method.getParameterAnnotations()) { 326 for (Annotation parameterAnnotation : parameterAnnotations) { 327 if (parameterAnnotation.annotationType() == Assisted.class) { 328 throw newConfigurationException( 329 "Factory method %s has an @Assisted parameter, which " 330 + "is incompatible with the deprecated @AssistedInject annotation. Please replace " 331 + "@AssistedInject with @Inject on the %s constructor.", 332 method, implementationType); 333 } 334 } 335 } 336 337 AssistedConstructor<?> matchingConstructor = paramsToConstructor.remove(methodParams); 338 339 result.put(method, matchingConstructor); 340 } 341 return result; 342 } 343 344 @Override getDependencies()345 public Set<Dependency<?>> getDependencies() { 346 List<Dependency<?>> dependencies = Lists.newArrayList(); 347 for (AssistedConstructor<?> constructor : factoryMethodToConstructor.values()) { 348 for (Parameter parameter : constructor.getAllParameters()) { 349 if (!parameter.isProvidedByFactory()) { 350 dependencies.add(Dependency.get(parameter.getPrimaryBindingKey())); 351 } 352 } 353 } 354 return ImmutableSet.copyOf(dependencies); 355 } 356 357 @Override get()358 public F get() { 359 InvocationHandler invocationHandler = 360 new InvocationHandler() { 361 @Override 362 public Object invoke(Object proxy, Method method, Object[] creationArgs) 363 throws Throwable { 364 // pass methods from Object.class to the proxy 365 if (method.getDeclaringClass().equals(Object.class)) { 366 if ("equals".equals(method.getName())) { 367 return proxy == creationArgs[0]; 368 } else if ("hashCode".equals(method.getName())) { 369 return System.identityHashCode(proxy); 370 } else { 371 return method.invoke(this, creationArgs); 372 } 373 } 374 375 AssistedConstructor<?> constructor = factoryMethodToConstructor.get(method); 376 Object[] constructorArgs = gatherArgsForConstructor(constructor, creationArgs); 377 Object objectToReturn = constructor.newInstance(constructorArgs); 378 injector.injectMembers(objectToReturn); 379 return objectToReturn; 380 } 381 382 public Object[] gatherArgsForConstructor( 383 AssistedConstructor<?> constructor, Object[] factoryArgs) { 384 int numParams = constructor.getAllParameters().size(); 385 int argPosition = 0; 386 Object[] result = new Object[numParams]; 387 388 for (int i = 0; i < numParams; i++) { 389 Parameter parameter = constructor.getAllParameters().get(i); 390 if (parameter.isProvidedByFactory()) { 391 result[i] = factoryArgs[argPosition]; 392 argPosition++; 393 } else { 394 result[i] = parameter.getValue(injector); 395 } 396 } 397 return result; 398 } 399 }; 400 401 @SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class<T> 402 Class<F> factoryRawType = (Class<F>) (Class<?>) factoryType.getRawType(); 403 return factoryRawType.cast( 404 Proxy.newProxyInstance( 405 BytecodeGen.getClassLoader(factoryRawType), 406 new Class[] {factoryRawType}, 407 invocationHandler)); 408 } 409 410 @Override hashCode()411 public int hashCode() { 412 return Objects.hashCode(factoryType, implementationType); 413 } 414 415 @Override equals(Object obj)416 public boolean equals(Object obj) { 417 if (!(obj instanceof FactoryProvider)) { 418 return false; 419 } 420 FactoryProvider<?> other = (FactoryProvider<?>) obj; 421 return factoryType.equals(other.factoryType) 422 && implementationType.equals(other.implementationType); 423 } 424 newConfigurationException(String format, Object... args)425 private static ConfigurationException newConfigurationException(String format, Object... args) { 426 return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args)))); 427 } 428 } 429