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