377 lines
17 KiB
Java
377 lines
17 KiB
Java
|
package com.google.inject.assistedinject;
|
||
|
|
||
|
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.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;
|
||
|
|
||
|
import static com.google.inject.internal.Annotations.getKey;
|
||
|
|
||
|
/**
|
||
|
* <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
|
||
|
* @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 final TypeLiteral<F> factoryType;
|
||
|
private final TypeLiteral<?> implementationType;
|
||
|
private final Map<Method, AssistedConstructor<?>> factoryMethodToConstructor;
|
||
|
private Injector injector;
|
||
|
|
||
|
private FactoryProvider(TypeLiteral<F> factoryType,
|
||
|
TypeLiteral<?> implementationType,
|
||
|
Map<Method, AssistedConstructor<?>> factoryMethodToConstructor) {
|
||
|
this.factoryType = factoryType;
|
||
|
this.implementationType = implementationType;
|
||
|
this.factoryMethodToConstructor = factoryMethodToConstructor;
|
||
|
checkDeclaredExceptionsMatch();
|
||
|
}
|
||
|
|
||
|
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 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;
|
||
|
}
|
||
|
|
||
|
private static ConfigurationException newConfigurationException(String format, Object... args) {
|
||
|
return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args))));
|
||
|
}
|
||
|
|
||
|
@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);
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
public F get() {
|
||
|
InvocationHandler invocationHandler = new InvocationHandler() {
|
||
|
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(factoryRawType.getClassLoader(),
|
||
|
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);
|
||
|
}
|
||
|
}
|