/* * Copyright (C) 2007 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.inject.assistedinject; import static com.google.inject.internal.Annotations.getKey; import com.google.common.base.Objects; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.inject.ConfigurationException; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.internal.BytecodeGen; import com.google.inject.internal.Errors; import com.google.inject.internal.ErrorsException; import com.google.inject.spi.Dependency; import com.google.inject.spi.HasDependencies; import com.google.inject.spi.Message; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.Type; import java.util.List; import java.util.Map; import java.util.Set; /** * <strong>Obsolete.</strong> Prefer {@link FactoryModuleBuilder} for its more concise API and * additional capability. * * <p>Provides a factory that combines the caller's arguments with injector-supplied values to * construct objects. * * <h3>Defining a factory</h3> * * Create an interface whose methods return the constructed type, or any of its supertypes. The * method's parameters are the arguments required to build the constructed type. * * <pre>public interface PaymentFactory { * Payment create(Date startDate, Money amount); * }</pre> * * You can name your factory methods whatever you like, such as <i>create</i>, <i>createPayment</i> * or <i>newPayment</i>. * * <h3>Creating a type that accepts factory parameters</h3> * * {@code constructedType} is a concrete class with an {@literal @}{@link Inject}-annotated * constructor. In addition to injector-supplied parameters, the constructor should have parameters * that match each of the factory method's parameters. Each factory-supplied parameter requires an * {@literal @}{@link Assisted} annotation. This serves to document that the parameter is not bound * by your application's modules. * * <pre>public class RealPayment implements Payment { * {@literal @}Inject * public RealPayment( * CreditService creditService, * AuthService authService, * <strong>{@literal @}Assisted Date startDate</strong>, * <strong>{@literal @}Assisted Money amount</strong>) { * ... * } * }</pre> * * Any parameter that permits a null value should also be annotated {@code @Nullable}. * * <h3>Configuring factories</h3> * * In your {@link com.google.inject.Module module}, bind the factory interface to the returned * factory: * * <pre>bind(PaymentFactory.class).toProvider( * FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));</pre> * * As a side-effect of this binding, Guice will inject the factory to initialize it for use. The * factory cannot be used until the injector has been initialized. * * <h3>Using the factory</h3> * * Inject your factory into your application classes. When you use the factory, your arguments will * be combined with values from the injector to construct an instance. * * <pre>public class PaymentAction { * {@literal @}Inject private PaymentFactory paymentFactory; * * public void doPayment(Money amount) { * Payment payment = paymentFactory.create(new Date(), amount); * payment.apply(); * } * }</pre> * * <h3>Making parameter types distinct</h3> * * The types of the factory method's parameters must be distinct. To use multiple parameters of the * same type, use a named {@literal @}{@link Assisted} annotation to disambiguate the parameters. * The names must be applied to the factory method's parameters: * * <pre>public interface PaymentFactory { * Payment create( * <strong>{@literal @}Assisted("startDate")</strong> Date startDate, * <strong>{@literal @}Assisted("dueDate")</strong> Date dueDate, * Money amount); * } </pre> * * ...and to the concrete type's constructor parameters: * * <pre>public class RealPayment implements Payment { * {@literal @}Inject * public RealPayment( * CreditService creditService, * AuthService authService, * <strong>{@literal @}Assisted("startDate")</strong> Date startDate, * <strong>{@literal @}Assisted("dueDate")</strong> Date dueDate, * <strong>{@literal @}Assisted</strong> Money amount) { * ... * } * }</pre> * * <h3>Values are created by Guice</h3> * * Returned factories use child injectors to create values. The values are eligible for method * interception. In addition, {@literal @}{@literal Inject} members will be injected before they are * returned. * * <h3>Backwards compatibility using {@literal @}AssistedInject</h3> * * Instead of the {@literal @}Inject annotation, you may annotate the constructed classes with * {@literal @}{@link AssistedInject}. This triggers a limited backwards-compatability mode. * * <p>Instead of matching factory method arguments to constructor parameters using their names, the * <strong>parameters are matched by their order</strong>. The first factory method argument is used * for the first {@literal @}Assisted constructor parameter, etc.. Annotation names have no effect. * * <p>Returned values are <strong>not created by Guice</strong>. These types are not eligible for * method interception. They do receive post-construction member injection. * * @param <F> The factory interface * @author jmourits@google.com (Jerome Mourits) * @author jessewilson@google.com (Jesse Wilson) * @author dtm@google.com (Daniel Martin) * @deprecated use {@link FactoryModuleBuilder} instead. */ @Deprecated public class FactoryProvider<F> implements Provider<F>, HasDependencies { /* * This class implements the old @AssistedInject implementation that manually matches constructors * to factory methods. The new child injector implementation lives in FactoryProvider2. */ private Injector injector; private final TypeLiteral<F> factoryType; private final TypeLiteral<?> implementationType; private final Map<Method, AssistedConstructor<?>> factoryMethodToConstructor; public static <F> Provider<F> newFactory(Class<F> factoryType, Class<?> implementationType) { return newFactory(TypeLiteral.get(factoryType), TypeLiteral.get(implementationType)); } public static <F> Provider<F> newFactory( TypeLiteral<F> factoryType, TypeLiteral<?> implementationType) { Map<Method, AssistedConstructor<?>> factoryMethodToConstructor = createMethodMapping(factoryType, implementationType); if (!factoryMethodToConstructor.isEmpty()) { return new FactoryProvider<F>(factoryType, implementationType, factoryMethodToConstructor); } else { BindingCollector collector = new BindingCollector(); // Preserving backwards-compatibility: Map all return types in a factory // interface to the passed implementation type. Errors errors = new Errors(); Key<?> implementationKey = Key.get(implementationType); try { for (Method method : factoryType.getRawType().getMethods()) { Key<?> returnType = getKey(factoryType.getReturnType(method), method, method.getAnnotations(), errors); if (!implementationKey.equals(returnType)) { collector.addBinding(returnType, implementationType); } } } catch (ErrorsException e) { throw new ConfigurationException(e.getErrors().getMessages()); } return new FactoryProvider2<F>(Key.get(factoryType), collector); } } private FactoryProvider( TypeLiteral<F> factoryType, TypeLiteral<?> implementationType, Map<Method, AssistedConstructor<?>> factoryMethodToConstructor) { this.factoryType = factoryType; this.implementationType = implementationType; this.factoryMethodToConstructor = factoryMethodToConstructor; checkDeclaredExceptionsMatch(); } @Inject void setInjectorAndCheckUnboundParametersAreInjectable(Injector injector) { this.injector = injector; for (AssistedConstructor<?> c : factoryMethodToConstructor.values()) { for (Parameter p : c.getAllParameters()) { if (!p.isProvidedByFactory() && !paramCanBeInjected(p, injector)) { // this is lame - we're not using the proper mechanism to add an // error to the injector. Throughout this class we throw exceptions // to add errors, which isn't really the best way in Guice throw newConfigurationException( "Parameter of type '%s' is not injectable or annotated " + "with @Assisted for Constructor '%s'", p, c); } } } } private void checkDeclaredExceptionsMatch() { for (Map.Entry<Method, AssistedConstructor<?>> entry : factoryMethodToConstructor.entrySet()) { for (Class<?> constructorException : entry.getValue().getDeclaredExceptions()) { if (!isConstructorExceptionCompatibleWithFactoryExeception( constructorException, entry.getKey().getExceptionTypes())) { throw newConfigurationException( "Constructor %s declares an exception, but no compatible " + "exception is thrown by the factory method %s", entry.getValue(), entry.getKey()); } } } } private boolean isConstructorExceptionCompatibleWithFactoryExeception( Class<?> constructorException, Class<?>[] factoryExceptions) { for (Class<?> factoryException : factoryExceptions) { if (factoryException.isAssignableFrom(constructorException)) { return true; } } return false; } private boolean paramCanBeInjected(Parameter parameter, Injector injector) { return parameter.isBound(injector); } private static Map<Method, AssistedConstructor<?>> createMethodMapping( TypeLiteral<?> factoryType, TypeLiteral<?> implementationType) { List<AssistedConstructor<?>> constructors = Lists.newArrayList(); for (Constructor<?> constructor : implementationType.getRawType().getDeclaredConstructors()) { if (constructor.isAnnotationPresent(AssistedInject.class)) { AssistedConstructor<?> assistedConstructor = AssistedConstructor.create( constructor, implementationType.getParameterTypes(constructor)); constructors.add(assistedConstructor); } } if (constructors.isEmpty()) { return ImmutableMap.of(); } Method[] factoryMethods = factoryType.getRawType().getMethods(); if (constructors.size() != factoryMethods.length) { throw newConfigurationException( "Constructor mismatch: %s has %s @AssistedInject " + "constructors, factory %s has %s creation methods", implementationType, constructors.size(), factoryType, factoryMethods.length); } Map<ParameterListKey, AssistedConstructor<?>> paramsToConstructor = Maps.newHashMap(); for (AssistedConstructor<?> c : constructors) { if (paramsToConstructor.containsKey(c.getAssistedParameters())) { throw new RuntimeException("Duplicate constructor, " + c); } paramsToConstructor.put(c.getAssistedParameters(), c); } Map<Method, AssistedConstructor<?>> result = Maps.newHashMap(); for (Method method : factoryMethods) { if (!method.getReturnType().isAssignableFrom(implementationType.getRawType())) { throw newConfigurationException( "Return type of method %s is not assignable from %s", method, implementationType); } List<Type> parameterTypes = Lists.newArrayList(); for (TypeLiteral<?> parameterType : factoryType.getParameterTypes(method)) { parameterTypes.add(parameterType.getType()); } ParameterListKey methodParams = new ParameterListKey(parameterTypes); if (!paramsToConstructor.containsKey(methodParams)) { throw newConfigurationException( "%s has no @AssistInject constructor that takes the " + "@Assisted parameters %s in that order. @AssistInject constructors are %s", implementationType, methodParams, paramsToConstructor.values()); } method.getParameterAnnotations(); for (Annotation[] parameterAnnotations : method.getParameterAnnotations()) { for (Annotation parameterAnnotation : parameterAnnotations) { if (parameterAnnotation.annotationType() == Assisted.class) { throw newConfigurationException( "Factory method %s has an @Assisted parameter, which " + "is incompatible with the deprecated @AssistedInject annotation. Please replace " + "@AssistedInject with @Inject on the %s constructor.", method, implementationType); } } } AssistedConstructor<?> matchingConstructor = paramsToConstructor.remove(methodParams); result.put(method, matchingConstructor); } return result; } @Override public Set<Dependency<?>> getDependencies() { List<Dependency<?>> dependencies = Lists.newArrayList(); for (AssistedConstructor<?> constructor : factoryMethodToConstructor.values()) { for (Parameter parameter : constructor.getAllParameters()) { if (!parameter.isProvidedByFactory()) { dependencies.add(Dependency.get(parameter.getPrimaryBindingKey())); } } } return ImmutableSet.copyOf(dependencies); } @Override public F get() { InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] creationArgs) throws Throwable { // pass methods from Object.class to the proxy if (method.getDeclaringClass().equals(Object.class)) { if ("equals".equals(method.getName())) { return proxy == creationArgs[0]; } else if ("hashCode".equals(method.getName())) { return System.identityHashCode(proxy); } else { return method.invoke(this, creationArgs); } } AssistedConstructor<?> constructor = factoryMethodToConstructor.get(method); Object[] constructorArgs = gatherArgsForConstructor(constructor, creationArgs); Object objectToReturn = constructor.newInstance(constructorArgs); injector.injectMembers(objectToReturn); return objectToReturn; } public Object[] gatherArgsForConstructor( AssistedConstructor<?> constructor, Object[] factoryArgs) { int numParams = constructor.getAllParameters().size(); int argPosition = 0; Object[] result = new Object[numParams]; for (int i = 0; i < numParams; i++) { Parameter parameter = constructor.getAllParameters().get(i); if (parameter.isProvidedByFactory()) { result[i] = factoryArgs[argPosition]; argPosition++; } else { result[i] = parameter.getValue(injector); } } return result; } }; @SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class<T> Class<F> factoryRawType = (Class<F>) (Class<?>) factoryType.getRawType(); return factoryRawType.cast( Proxy.newProxyInstance( BytecodeGen.getClassLoader(factoryRawType), new Class[] {factoryRawType}, invocationHandler)); } @Override public int hashCode() { return Objects.hashCode(factoryType, implementationType); } @Override public boolean equals(Object obj) { if (!(obj instanceof FactoryProvider)) { return false; } FactoryProvider<?> other = (FactoryProvider<?>) obj; return factoryType.equals(other.factoryType) && implementationType.equals(other.implementationType); } private static ConfigurationException newConfigurationException(String format, Object... args) { return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args)))); } }