added assistedinject and multibindings extension

This commit is contained in:
Jörg Prante 2016-01-07 23:04:58 +01:00
parent 18da30f945
commit 3d4887f08c
42 changed files with 13209 additions and 0 deletions

View file

@ -0,0 +1,26 @@
package com.google.inject.assistedinject;
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotates an injected parameter or field whose value comes from an argument to a factory method.
*/
@BindingAnnotation
@Target({FIELD, PARAMETER, METHOD})
@Retention(RUNTIME)
public @interface Assisted {
/**
* The unique name for this parameter. This is matched to the {@literal @Assisted} constructor
* parameter with the same value. Names are not necessary when the parameter types are distinct.
*/
String value() default "";
}

View file

@ -0,0 +1,89 @@
package com.google.inject.assistedinject;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.TypeLiteral;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Internal respresentation of a constructor annotated with
* {@link AssistedInject}
*/
class AssistedConstructor<T> {
private final Constructor<T> constructor;
private final ParameterListKey assistedParameters;
private final List<Parameter> allParameters;
private AssistedConstructor(Constructor<T> constructor, List<TypeLiteral<?>> parameterTypes) {
this.constructor = constructor;
Annotation[][] annotations = constructor.getParameterAnnotations();
List<Type> typeList = Lists.newArrayList();
allParameters = new ArrayList<Parameter>();
// categorize params as @Assisted or @Injected
for (int i = 0; i < parameterTypes.size(); i++) {
Parameter parameter = new Parameter(parameterTypes.get(i).getType(), annotations[i]);
allParameters.add(parameter);
if (parameter.isProvidedByFactory()) {
typeList.add(parameter.getType());
}
}
this.assistedParameters = new ParameterListKey(typeList);
}
public static <T> AssistedConstructor<T> create(
Constructor<T> constructor, List<TypeLiteral<?>> parameterTypes) {
return new AssistedConstructor<T>(constructor, parameterTypes);
}
/**
* Returns the {@link ParameterListKey} for this constructor. The
* {@link ParameterListKey} is created from the ordered list of {@link Assisted}
* constructor parameters.
*/
public ParameterListKey getAssistedParameters() {
return assistedParameters;
}
/**
* Returns an ordered list of all constructor parameters (both
* {@link Assisted} and {@link Inject}ed).
*/
public List<Parameter> getAllParameters() {
return allParameters;
}
public Set<Class<?>> getDeclaredExceptions() {
return new HashSet<Class<?>>(Arrays.asList(constructor.getExceptionTypes()));
}
/**
* Returns an instance of T, constructed using this constructor, with the
* supplied arguments.
*/
public T newInstance(Object[] args) throws Throwable {
constructor.setAccessible(true);
try {
return constructor.newInstance(args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
@Override
public String toString() {
return constructor.toString();
}
}

View file

@ -0,0 +1,28 @@
package com.google.inject.assistedinject;
import com.google.inject.Inject;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* <p>
* When used in tandem with {@link FactoryModuleBuilder}, constructors annotated with
* {@code @AssistedInject} indicate that multiple constructors can be injected, each with different
* parameters. AssistedInject annotations should not be mixed with {@literal @}{@link Inject}
* annotations. The assisted parameters must exactly match one corresponding factory method within
* the factory interface, but the parameters do not need to be in the same order. Constructors
* annotated with AssistedInject <b>are</b> created by Guice and receive all the benefits
* (such as AOP).
*
* <p>
* Constructor parameters must be either supplied by the factory interface and marked with
* <code>@Assisted</code>, or they must be injectable.
*/
@Target({CONSTRUCTOR})
@Retention(RUNTIME)
public @interface AssistedInject {
}

View file

@ -0,0 +1,23 @@
package com.google.inject.assistedinject;
import com.google.inject.Key;
import java.util.Collection;
/**
* A binding for a factory created by FactoryModuleBuilder.
*
* @param <T> The fully qualified type of the factory.
*/
public interface AssistedInjectBinding<T> {
/**
* Returns the {@link Key} for the factory binding.
*/
Key<T> getKey();
/**
* Returns an {@link AssistedMethod} for each method in the factory.
*/
Collection<AssistedMethod> getAssistedMethods();
}

View file

@ -0,0 +1,17 @@
package com.google.inject.assistedinject;
import com.google.inject.spi.BindingTargetVisitor;
/**
* A visitor for the AssistedInject extension.
* <p>
* If your {@link BindingTargetVisitor} implements this interface, bindings created by using
* {@link FactoryModuleBuilder} will be visited through this interface.
*/
public interface AssistedInjectTargetVisitor<T, V> extends BindingTargetVisitor<T, V> {
/**
* Visits an {@link AssistedInjectBinding} created through {@link FactoryModuleBuilder}.
*/
V visit(AssistedInjectBinding<? extends T> assistedInjectBinding);
}

View file

@ -0,0 +1,37 @@
package com.google.inject.assistedinject;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.Dependency;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Set;
/**
* Details about how a method in an assisted inject factory will be assisted.
*/
public interface AssistedMethod {
/**
* Returns the factory method that is being assisted.
*/
Method getFactoryMethod();
/**
* Returns the implementation type that will be created when the method is
* used.
*/
TypeLiteral<?> getImplementationType();
/**
* Returns the constructor that will be used to construct instances of the
* implementation.
*/
Constructor<?> getImplementationConstructor();
/**
* Returns all non-assisted dependencies required to construct and inject
* the implementation.
*/
Set<Dependency<?>> getDependencies();
}

View file

@ -0,0 +1,44 @@
package com.google.inject.assistedinject;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.inject.ConfigurationException;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.Message;
import java.util.Collections;
import java.util.Map;
/**
* Utility class for collecting factory bindings. Used for configuring {@link FactoryProvider2}.
*/
class BindingCollector {
private final Map<Key<?>, TypeLiteral<?>> bindings = Maps.newHashMap();
public BindingCollector addBinding(Key<?> key, TypeLiteral<?> target) {
if (bindings.containsKey(key)) {
throw new ConfigurationException(ImmutableSet.of(
new Message("Only one implementation can be specified for " + key)));
}
bindings.put(key, target);
return this;
}
public Map<Key<?>, TypeLiteral<?>> getBindings() {
return Collections.unmodifiableMap(bindings);
}
@Override
public int hashCode() {
return bindings.hashCode();
}
@Override
public boolean equals(Object obj) {
return (obj instanceof BindingCollector) && bindings.equals(((BindingCollector) obj).bindings);
}
}

View file

@ -0,0 +1,321 @@
package com.google.inject.assistedinject;
import com.google.inject.AbstractModule;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import java.lang.annotation.Annotation;
/**
* 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 com.google.inject.Inject
* 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>
*
* <h3>Multiple factory methods for the same type</h3>
* If the factory contains many methods that return the same type, you can create multiple
* constructors in your concrete class, each constructor marked with with
* {@literal @}{@link AssistedInject}, in order to match the different parameters types of the
* factory methods.
*
* <pre>public interface PaymentFactory {
* Payment create(Date startDate, Money amount);
* Payment createWithoutDate(Money amount);
* }
*
* public class RealPayment implements Payment {
* {@literal @}AssistedInject
* public RealPayment(
* CreditService creditService,
* AuthService authService,
* <strong>{@literal @}Assisted Date startDate</strong>,
* <strong>{@literal @}Assisted Money amount</strong>) {
* ...
* }
*
* {@literal @}AssistedInject
* public RealPayment(
* CreditService creditService,
* AuthService authService,
* <strong>{@literal @}Assisted Money amount</strong>) {
* ...
* }
* }</pre>
*
* <h3>Configuring simple factories</h3>
* In your {@link Module module}, install a {@code FactoryModuleBuilder} that creates the
* factory:
*
* <pre>install(new FactoryModuleBuilder()
* .implement(Payment.class, RealPayment.class)
* .build(PaymentFactory.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>Configuring complex factories</h3>
* Factories can create an arbitrary number of objects, one per each method. Each factory
* method can be configured using <code>.implement</code>.
*
* <pre>public interface OrderFactory {
* Payment create(Date startDate, Money amount);
* Shipment create(Customer customer, Item item);
* Receipt create(Payment payment, Shipment shipment);
* }
*
* [...]
*
* install(new FactoryModuleBuilder()
* .implement(Payment.class, RealPayment.class)
* // excluding .implement for Shipment means the implementation class
* // will be 'Shipment' itself, which is legal if it's not an interface.
* .implement(Receipt.class, RealReceipt.class)
* .build(OrderFactory.class));</pre>
* </pre>
*
* <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>More configuration options</h3>
* In addition to simply specifying an implementation class for any returned type, factories' return
* values can be automatic or can be configured to use annotations:
* <p/>
* If you just want to return the types specified in the factory, do not configure any
* implementations:
*
* <pre>public interface FruitFactory {
* Apple getApple(Color color);
* }
* ...
* protected void configure() {
* install(new FactoryModuleBuilder().build(FruitFactory.class));
* }</pre>
*
* Note that any type returned by the factory in this manner needs to be an implementation class.
* <p/>
* To return two different implementations for the same interface from your factory, use binding
* annotations on your return types:
*
* <pre>interface CarFactory {
* {@literal @}Named("fast") Car getFastCar(Color color);
* {@literal @}Named("clean") Car getCleanCar(Color color);
* }
* ...
* protected void configure() {
* install(new FactoryModuleBuilder()
* .implement(Car.class, Names.named("fast"), Porsche.class)
* .implement(Car.class, Names.named("clean"), Prius.class)
* .build(CarFactory.class));
* }</pre>
*
* <h3>Implementation limitations</h3>
* As a limitation of the implementation, it is prohibited to declare a factory method that
* accepts a {@code Provider} as one of its arguments.
*/
public final class FactoryModuleBuilder {
private final BindingCollector bindings = new BindingCollector();
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(Class<T> source, Class<? extends T> target) {
return implement(source, TypeLiteral.get(target));
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(Class<T> source, TypeLiteral<? extends T> target) {
return implement(TypeLiteral.get(source), target);
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source, Class<? extends T> target) {
return implement(source, TypeLiteral.get(target));
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source,
TypeLiteral<? extends T> target) {
return implement(Key.get(source), target);
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(Class<T> source, Annotation annotation,
Class<? extends T> target) {
return implement(source, annotation, TypeLiteral.get(target));
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(Class<T> source, Annotation annotation,
TypeLiteral<? extends T> target) {
return implement(TypeLiteral.get(source), annotation, target);
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source, Annotation annotation,
Class<? extends T> target) {
return implement(source, annotation, TypeLiteral.get(target));
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source, Annotation annotation,
TypeLiteral<? extends T> target) {
return implement(Key.get(source, annotation), target);
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(Class<T> source,
Class<? extends Annotation> annotationType, Class<? extends T> target) {
return implement(source, annotationType, TypeLiteral.get(target));
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(Class<T> source,
Class<? extends Annotation> annotationType, TypeLiteral<? extends T> target) {
return implement(TypeLiteral.get(source), annotationType, target);
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source,
Class<? extends Annotation> annotationType, Class<? extends T> target) {
return implement(source, annotationType, TypeLiteral.get(target));
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source,
Class<? extends Annotation> annotationType, TypeLiteral<? extends T> target) {
return implement(Key.get(source, annotationType), target);
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(Key<T> source, Class<? extends T> target) {
return implement(source, TypeLiteral.get(target));
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(Key<T> source, TypeLiteral<? extends T> target) {
bindings.addBinding(source, target);
return this;
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <F> Module build(Class<F> factoryInterface) {
return build(TypeLiteral.get(factoryInterface));
}
/**
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <F> Module build(TypeLiteral<F> factoryInterface) {
return build(Key.get(factoryInterface));
}
public <F> Module build(final Key<F> factoryInterface) {
return new AbstractModule() {
@Override
protected void configure() {
Provider<F> provider = new FactoryProvider2<F>(factoryInterface, bindings);
bind(factoryInterface).toProvider(provider);
}
};
}
}

View file

@ -0,0 +1,377 @@
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);
}
}

View file

@ -0,0 +1,932 @@
package com.google.inject.assistedinject;
import com.google.common.base.Objects;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.ConfigurationException;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.internal.util.Classes;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.Message;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.Toolable;
import com.google.inject.util.Providers;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;
/**
* The newer implementation of factory provider. This implementation uses a child injector to
* create values.
*/
final class FactoryProvider2<F> implements InvocationHandler,
ProviderWithExtensionVisitor<F>, HasDependencies, AssistedInjectBinding<F> {
/**
* A constant annotation to denote the return value, instead of creating a new one each time.
*/
static final Annotation RETURN_ANNOTATION = UniqueAnnotations.create();
// use the logger under a well-known name, not FactoryProvider2
static final Logger logger = Logger.getLogger(AssistedInject.class.getName());
/**
* if a factory method parameter isn't annotated, it gets this annotation.
*/
static final Assisted DEFAULT_ANNOTATION = new Assisted() {
public String value() {
return "";
}
public Class<? extends Annotation> annotationType() {
return Assisted.class;
}
@Override
public boolean equals(Object o) {
return o instanceof Assisted && ((Assisted) o).value().isEmpty();
}
@Override
public int hashCode() {
return 127 * "value".hashCode() ^ "".hashCode();
}
@Override
public String toString() {
return "@" + Assisted.class.getName() + "(value=)";
}
};
/**
* Mapping from method to the data about how the method will be assisted.
*/
private final ImmutableMap<Method, AssistData> assistDataByMethod;
/**
* Mapping from method to method handle, for generated default methods.
*/
private final ImmutableMap<Method, MethodHandleWrapper> methodHandleByMethod;
/**
* the factory interface, implemented and provided
*/
private final F factory;
/**
* The key that this is bound to.
*/
private final Key<F> factoryKey;
/**
* The binding collector, for equality/hashing purposes.
*/
private final BindingCollector collector;
/**
* the hosting injector, or null if we haven't been initialized yet
*/
private Injector injector;
/**
* @param factoryKey a key for a Java interface that defines one or more create methods.
* @param collector binding configuration that maps method return types to
* implementation types.
*/
FactoryProvider2(Key<F> factoryKey, BindingCollector collector) {
this.factoryKey = factoryKey;
this.collector = collector;
TypeLiteral<F> factoryType = factoryKey.getTypeLiteral();
Errors errors = new Errors();
@SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class<T>
Class<F> factoryRawType = (Class<F>) (Class<?>) factoryType.getRawType();
try {
if (!factoryRawType.isInterface()) {
throw errors.addMessage("%s must be an interface.", factoryRawType).toException();
}
Multimap<String, Method> defaultMethods = HashMultimap.create();
Multimap<String, Method> otherMethods = HashMultimap.create();
ImmutableMap.Builder<Method, AssistData> assistDataBuilder = ImmutableMap.builder();
// TODO: also grab methods from superinterfaces
for (Method method : factoryRawType.getMethods()) {
// Skip default methods that java8 may have created.
if (isDefault(method) && (method.isBridge() || method.isSynthetic())) {
// Even synthetic default methods need the return type validation...
// unavoidable consequence of javac8. :-(
validateFactoryReturnType(errors, method.getReturnType(), factoryRawType);
defaultMethods.put(method.getName(), method);
continue;
}
otherMethods.put(method.getName(), method);
TypeLiteral<?> returnTypeLiteral = factoryType.getReturnType(method);
Key<?> returnType;
try {
returnType = Annotations.getKey(returnTypeLiteral, method, method.getAnnotations(), errors);
} catch (ConfigurationException ce) {
// If this was an error due to returnTypeLiteral not being specified, rephrase
// it as our factory not being specified, so it makes more sense to users.
if (isTypeNotSpecified(returnTypeLiteral, ce)) {
throw errors.keyNotFullySpecified(TypeLiteral.get(factoryRawType)).toException();
} else {
throw ce;
}
}
validateFactoryReturnType(errors, returnType.getTypeLiteral().getRawType(), factoryRawType);
List<TypeLiteral<?>> params = factoryType.getParameterTypes(method);
Annotation[][] paramAnnotations = method.getParameterAnnotations();
int p = 0;
List<Key<?>> keys = Lists.newArrayList();
for (TypeLiteral<?> param : params) {
Key<?> paramKey = Annotations.getKey(param, method, paramAnnotations[p++], errors);
Class<?> underlylingType = paramKey.getTypeLiteral().getRawType();
if (underlylingType.equals(Provider.class)
|| underlylingType.equals(javax.inject.Provider.class)) {
errors.addMessage("A Provider may not be a type in a factory method of an AssistedInject."
+ "\n Offending instance is parameter [%s] with key [%s] on method [%s]",
p, paramKey, method);
}
keys.add(assistKey(method, paramKey, errors));
}
ImmutableList<Key<?>> immutableParamList = ImmutableList.copyOf(keys);
// try to match up the method to the constructor
TypeLiteral<?> implementation = collector.getBindings().get(returnType);
if (implementation == null) {
implementation = returnType.getTypeLiteral();
}
Class<? extends Annotation> scope =
Annotations.findScopeAnnotation(errors, implementation.getRawType());
if (scope != null) {
errors.addMessage("Found scope annotation [%s] on implementation class "
+ "[%s] of AssistedInject factory [%s].\nThis is not allowed, please"
+ " remove the scope annotation.",
scope, implementation.getRawType(), factoryType);
}
InjectionPoint ctorInjectionPoint;
try {
ctorInjectionPoint =
findMatchingConstructorInjectionPoint(method, returnType, implementation, immutableParamList);
} catch (ErrorsException ee) {
errors.merge(ee.getErrors());
continue;
}
Constructor<?> constructor = (Constructor<?>) ctorInjectionPoint.getMember();
List<ThreadLocalProvider> providers = Collections.emptyList();
Set<Dependency<?>> deps = getDependencies(ctorInjectionPoint, implementation);
boolean optimized = false;
// Now go through all dependencies of the implementation and see if it is OK to
// use an optimized form of assistedinject2. The optimized form requires that
// all injections directly inject the object itself (and not a Provider of the object,
// or an Injector), because it caches a single child injector and mutates the Provider
// of the arguments in a ThreadLocal.
if (isValidForOptimizedAssistedInject(deps, implementation.getRawType(), factoryType)) {
ImmutableList.Builder<ThreadLocalProvider> providerListBuilder = ImmutableList.builder();
for (int i = 0; i < params.size(); i++) {
providerListBuilder.add(new ThreadLocalProvider());
}
providers = providerListBuilder.build();
optimized = true;
}
AssistData data = new AssistData(constructor,
returnType,
immutableParamList,
implementation,
method,
removeAssistedDeps(deps),
optimized,
providers);
assistDataBuilder.put(method, data);
}
factory = factoryRawType.cast(Proxy.newProxyInstance(
factoryRawType.getClassLoader(), new Class<?>[]{factoryRawType}, this));
// Now go back through default methods. Try to use MethodHandles to make things
// work. If that doesn't work, fallback to trying to find compatible method
// signatures.
Map<Method, AssistData> dataSoFar = assistDataBuilder.build();
ImmutableMap.Builder<Method, MethodHandleWrapper> methodHandleBuilder = ImmutableMap.builder();
for (Map.Entry<String, Method> entry : defaultMethods.entries()) {
Method defaultMethod = entry.getValue();
MethodHandleWrapper handle = MethodHandleWrapper.create(defaultMethod, factory);
if (handle != null) {
methodHandleBuilder.put(defaultMethod, handle);
} else {
boolean foundMatch = false;
for (Method otherMethod : otherMethods.get(defaultMethod.getName())) {
if (dataSoFar.containsKey(otherMethod) && isCompatible(defaultMethod, otherMethod)) {
if (foundMatch) {
errors.addMessage("Generated default method %s with parameters %s is"
+ " signature-compatible with more than one non-default method."
+ " Unable to create factory. As a workaround, remove the override"
+ " so javac stops generating a default method.",
defaultMethod, Arrays.asList(defaultMethod.getParameterTypes()));
} else {
assistDataBuilder.put(defaultMethod, dataSoFar.get(otherMethod));
foundMatch = true;
}
}
}
if (!foundMatch) {
throw new IllegalStateException("Can't find method compatible with: " + defaultMethod);
}
}
}
// If we generated any errors (from finding matching constructors, for instance), throw an exception.
if (errors.hasErrors()) {
throw errors.toException();
}
assistDataByMethod = assistDataBuilder.build();
methodHandleByMethod = methodHandleBuilder.build();
} catch (ErrorsException e) {
throw new ConfigurationException(e.getErrors().getMessages());
}
}
static boolean isDefault(Method method) {
// Per the javadoc, default methods are non-abstract, public, non-static.
// They're also in interfaces, but we can guarantee that already since we only act
// on interfaces.
return (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC))
== Modifier.PUBLIC;
}
/**
* Returns true if {@code thrown} can be thrown by {@code invoked} without wrapping.
*/
static boolean canRethrow(Method invoked, Throwable thrown) {
if (thrown instanceof Error || thrown instanceof RuntimeException) {
return true;
}
for (Class<?> declared : invoked.getExceptionTypes()) {
if (declared.isInstance(thrown)) {
return true;
}
}
return false;
}
private boolean isCompatible(Method src, Method dst) {
if (!src.getReturnType().isAssignableFrom(dst.getReturnType())) {
return false;
}
Class<?>[] srcParams = src.getParameterTypes();
Class<?>[] dstParams = dst.getParameterTypes();
if (srcParams.length != dstParams.length) {
return false;
}
for (int i = 0; i < srcParams.length; i++) {
if (!srcParams[i].isAssignableFrom(dstParams[i])) {
return false;
}
}
return true;
}
public F get() {
return factory;
}
public Set<Dependency<?>> getDependencies() {
Set<Dependency<?>> combinedDeps = new HashSet<Dependency<?>>();
for (AssistData data : assistDataByMethod.values()) {
combinedDeps.addAll(data.dependencies);
}
return ImmutableSet.copyOf(combinedDeps);
}
public Key<F> getKey() {
return factoryKey;
}
// Safe cast because values are typed to AssistedData, which is an AssistedMethod, and
// the collection is immutable.
@SuppressWarnings("unchecked")
public Collection<AssistedMethod> getAssistedMethods() {
return (Collection<AssistedMethod>) (Collection<?>) assistDataByMethod.values();
}
@SuppressWarnings("unchecked")
public <T, V> V acceptExtensionVisitor(BindingTargetVisitor<T, V> visitor,
ProviderInstanceBinding<? extends T> binding) {
if (visitor instanceof AssistedInjectTargetVisitor) {
return ((AssistedInjectTargetVisitor<T, V>) visitor).visit((AssistedInjectBinding<T>) this);
}
return visitor.visit(binding);
}
private void validateFactoryReturnType(Errors errors, Class<?> returnType, Class<?> factoryType) {
if (Modifier.isPublic(factoryType.getModifiers())
&& !Modifier.isPublic(returnType.getModifiers())) {
errors.addMessage("%s is public, but has a method that returns a non-public type: %s. "
+ "Due to limitations with java.lang.reflect.Proxy, this is not allowed. "
+ "Please either make the factory non-public or the return type public.",
factoryType, returnType);
}
}
/**
* Returns true if the ConfigurationException is due to an error of TypeLiteral not being fully
* specified.
*/
private boolean isTypeNotSpecified(TypeLiteral<?> typeLiteral, ConfigurationException ce) {
Collection<Message> messages = ce.getErrorMessages();
if (messages.size() == 1) {
Message msg = Iterables.getOnlyElement(
new Errors().keyNotFullySpecified(typeLiteral).getMessages());
return msg.getMessage().equals(Iterables.getOnlyElement(messages).getMessage());
} else {
return false;
}
}
/**
* Finds a constructor suitable for the method. If the implementation contained any constructors
* marked with {@link AssistedInject}, this requires all {@link Assisted} parameters to exactly
* match the parameters (in any order) listed in the method. Otherwise, if no
* {@link AssistedInject} constructors exist, this will default to looking for an
* {@literal @}{@link Inject} constructor.
*/
private <T> InjectionPoint findMatchingConstructorInjectionPoint(
Method method, Key<?> returnType, TypeLiteral<T> implementation, List<Key<?>> paramList)
throws ErrorsException {
Errors errors = new Errors(method);
if (returnType.getTypeLiteral().equals(implementation)) {
errors = errors.withSource(implementation);
} else {
errors = errors.withSource(returnType).withSource(implementation);
}
Class<?> rawType = implementation.getRawType();
if (Modifier.isInterface(rawType.getModifiers())) {
errors.addMessage(
"%s is an interface, not a concrete class. Unable to create AssistedInject factory.",
implementation);
throw errors.toException();
} else if (Modifier.isAbstract(rawType.getModifiers())) {
errors.addMessage(
"%s is abstract, not a concrete class. Unable to create AssistedInject factory.",
implementation);
throw errors.toException();
} else if (Classes.isInnerClass(rawType)) {
errors.cannotInjectInnerClass(rawType);
throw errors.toException();
}
Constructor<?> matchingConstructor = null;
boolean anyAssistedInjectConstructors = false;
// Look for AssistedInject constructors...
for (Constructor<?> constructor : rawType.getDeclaredConstructors()) {
if (constructor.isAnnotationPresent(AssistedInject.class)) {
anyAssistedInjectConstructors = true;
if (constructorHasMatchingParams(implementation, constructor, paramList, errors)) {
if (matchingConstructor != null) {
errors
.addMessage(
"%s has more than one constructor annotated with @AssistedInject"
+ " that matches the parameters in method %s. Unable to create "
+ "AssistedInject factory.",
implementation, method);
throw errors.toException();
} else {
matchingConstructor = constructor;
}
}
}
}
if (!anyAssistedInjectConstructors) {
// If none existed, use @Inject.
try {
return InjectionPoint.forConstructorOf(implementation);
} catch (ConfigurationException e) {
errors.merge(e.getErrorMessages());
throw errors.toException();
}
} else {
// Otherwise, use it or fail with a good error message.
if (matchingConstructor != null) {
// safe because we got the constructor from this implementation.
@SuppressWarnings("unchecked")
InjectionPoint ip = InjectionPoint.forConstructor(
(Constructor<? super T>) matchingConstructor, implementation);
return ip;
} else {
errors.addMessage(
"%s has @AssistedInject constructors, but none of them match the"
+ " parameters in method %s. Unable to create AssistedInject factory.",
implementation, method);
throw errors.toException();
}
}
}
/**
* Matching logic for constructors annotated with AssistedInject.
* This returns true if and only if all @Assisted parameters in the
* constructor exactly match (in any order) all @Assisted parameters
* the method's parameter.
*/
private boolean constructorHasMatchingParams(TypeLiteral<?> type,
Constructor<?> constructor, List<Key<?>> paramList, Errors errors)
throws ErrorsException {
List<TypeLiteral<?>> params = type.getParameterTypes(constructor);
Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
int p = 0;
List<Key<?>> constructorKeys = Lists.newArrayList();
for (TypeLiteral<?> param : params) {
Key<?> paramKey = Annotations.getKey(param, constructor, paramAnnotations[p++],
errors);
constructorKeys.add(paramKey);
}
// Require that every key exist in the constructor to match up exactly.
for (Key<?> key : paramList) {
// If it didn't exist in the constructor set, we can't use it.
if (!constructorKeys.remove(key)) {
return false;
}
}
// If any keys remain and their annotation is Assisted, we can't use it.
for (Key<?> key : constructorKeys) {
if (key.getAnnotationType() == Assisted.class) {
return false;
}
}
// All @Assisted params match up to the method's parameters.
return true;
}
/**
* Calculates all dependencies required by the implementation and constructor.
*/
private Set<Dependency<?>> getDependencies(InjectionPoint ctorPoint, TypeLiteral<?> implementation) {
ImmutableSet.Builder<Dependency<?>> builder = ImmutableSet.builder();
builder.addAll(ctorPoint.getDependencies());
if (!implementation.getRawType().isInterface()) {
for (InjectionPoint ip : InjectionPoint.forInstanceMethodsAndFields(implementation)) {
builder.addAll(ip.getDependencies());
}
}
return builder.build();
}
/**
* Return all non-assisted dependencies.
*/
private Set<Dependency<?>> removeAssistedDeps(Set<Dependency<?>> deps) {
ImmutableSet.Builder<Dependency<?>> builder = ImmutableSet.builder();
for (Dependency<?> dep : deps) {
Class<?> annotationType = dep.getKey().getAnnotationType();
if (annotationType == null || !annotationType.equals(Assisted.class)) {
builder.add(dep);
}
}
return builder.build();
}
/**
* Returns true if all dependencies are suitable for the optimized version of AssistedInject. The
* optimized version caches the binding & uses a ThreadLocal Provider, so can only be applied if
* the assisted bindings are immediately provided. This looks for hints that the values may be
* lazily retrieved, by looking for injections of Injector or a Provider for the assisted values.
*/
private boolean isValidForOptimizedAssistedInject(Set<Dependency<?>> dependencies,
Class<?> implementation, TypeLiteral<?> factoryType) {
Set<Dependency<?>> badDeps = null; // optimization: create lazily
for (Dependency<?> dep : dependencies) {
if (isInjectorOrAssistedProvider(dep)) {
if (badDeps == null) {
badDeps = Sets.newHashSet();
}
badDeps.add(dep);
}
}
if (badDeps != null && !badDeps.isEmpty()) {
logger.log(Level.WARNING, "AssistedInject factory {0} will be slow "
+ "because {1} has assisted Provider dependencies or injects the Injector. "
+ "Stop injecting @Assisted Provider<T> (instead use @Assisted T) "
+ "or Injector to speed things up. (It will be a ~6500% speed bump!) "
+ "The exact offending deps are: {2}",
new Object[]{factoryType, implementation, badDeps});
return false;
}
return true;
}
/**
* Returns true if the dependency is for {@link Injector} or if the dependency
* is a {@link Provider} for a parameter that is {@literal @}{@link Assisted}.
*/
private boolean isInjectorOrAssistedProvider(Dependency<?> dependency) {
Class<?> annotationType = dependency.getKey().getAnnotationType();
if (annotationType != null && annotationType.equals(Assisted.class)) { // If it's assisted..
if (dependency.getKey().getTypeLiteral().getRawType().equals(Provider.class)) { // And a Provider...
return true;
}
} else if (dependency.getKey().getTypeLiteral().getRawType().equals(Injector.class)) { // If it's the Injector...
return true;
}
return false;
}
/**
* Returns a key similar to {@code key}, but with an {@literal @}Assisted binding annotation.
* This fails if another binding annotation is clobbered in the process. If the key already has
* the {@literal @}Assisted annotation, it is returned as-is to preserve any String value.
*/
private <T> Key<T> assistKey(Method method, Key<T> key, Errors errors) throws ErrorsException {
if (key.getAnnotationType() == null) {
return Key.get(key.getTypeLiteral(), DEFAULT_ANNOTATION);
} else if (key.getAnnotationType() == Assisted.class) {
return key;
} else {
errors.withSource(method).addMessage(
"Only @Assisted is allowed for factory parameters, but found @%s",
key.getAnnotationType());
throw errors.toException();
}
}
/**
* At injector-creation time, we initialize the invocation handler. At this time we make sure
* all factory methods will be able to build the target types.
*/
@Inject
@Toolable
void initialize(Injector injector) {
if (this.injector != null) {
throw new ConfigurationException(ImmutableList.of(new Message(FactoryProvider2.class,
"Factories.create() factories may only be used in one Injector!")));
}
this.injector = injector;
for (Map.Entry<Method, AssistData> entry : assistDataByMethod.entrySet()) {
Method method = entry.getKey();
AssistData data = entry.getValue();
Object[] args;
if (!data.optimized) {
args = new Object[method.getParameterTypes().length];
Arrays.fill(args, "dummy object for validating Factories");
} else {
args = null; // won't be used -- instead will bind to data.providers.
}
getBindingFromNewInjector(method, args, data); // throws if the binding isn't properly configured
}
}
/**
* Creates a child injector that binds the args, and returns the binding for the method's result.
*/
public Binding<?> getBindingFromNewInjector(
final Method method, final Object[] args, final AssistData data) {
checkState(injector != null,
"Factories.create() factories cannot be used until they're initialized by Guice.");
final Key<?> returnType = data.returnType;
// We ignore any pre-existing binding annotation.
final Key<?> returnKey = Key.get(returnType.getTypeLiteral(), RETURN_ANNOTATION);
Module assistedModule = new AbstractModule() {
@Override
@SuppressWarnings({
"unchecked", "rawtypes"}) // raw keys are necessary for the args array and return value
protected void configure() {
Binder binder = binder().withSource(method);
int p = 0;
if (!data.optimized) {
for (Key<?> paramKey : data.paramTypes) {
// Wrap in a Provider to cover null, and to prevent Guice from injecting the parameter
binder.bind((Key) paramKey).toProvider(Providers.of(args[p++]));
}
} else {
for (Key<?> paramKey : data.paramTypes) {
// Bind to our ThreadLocalProviders.
binder.bind((Key) paramKey).toProvider(data.providers.get(p++));
}
}
Constructor constructor = data.constructor;
// Constructor *should* always be non-null here,
// but if it isn't, we'll end up throwing a fairly good error
// message for the user.
if (constructor != null) {
binder.bind(returnKey)
.toConstructor(constructor, (TypeLiteral) data.implementationType)
.in(Scopes.NO_SCOPE); // make sure we erase any scope on the implementation type
}
}
};
Injector forCreate = injector.createChildInjector(assistedModule);
Binding<?> binding = forCreate.getBinding(returnKey);
// If we have providers cached in data, cache the binding for future optimizations.
if (data.optimized) {
data.cachedBinding = binding;
}
return binding;
}
/**
* When a factory method is invoked, we create a child injector that binds all parameters, then
* use that to get an instance of the return type.
*/
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
// If we setup a method handle earlier for this method, call it.
// This is necessary for default methods that java8 creates, so we
// can call the default method implementation (and not our proxied version of it).
if (methodHandleByMethod.containsKey(method)) {
return methodHandleByMethod.get(method).invokeWithArguments(args);
}
if (method.getDeclaringClass().equals(Object.class)) {
if ("equals".equals(method.getName())) {
return proxy == args[0];
} else if ("hashCode".equals(method.getName())) {
return System.identityHashCode(proxy);
} else {
return method.invoke(this, args);
}
}
AssistData data = assistDataByMethod.get(method);
checkState(data != null, "No data for method: %s", method);
Provider<?> provider;
if (data.cachedBinding != null) { // Try to get optimized form...
provider = data.cachedBinding.getProvider();
} else {
provider = getBindingFromNewInjector(method, args, data).getProvider();
}
try {
int p = 0;
for (ThreadLocalProvider tlp : data.providers) {
tlp.set(args[p++]);
}
return provider.get();
} catch (ProvisionException e) {
// if this is an exception declared by the factory method, throw it as-is
if (e.getErrorMessages().size() == 1) {
Message onlyError = getOnlyElement(e.getErrorMessages());
Throwable cause = onlyError.getCause();
if (cause != null && canRethrow(method, cause)) {
throw cause;
}
}
throw e;
} finally {
for (ThreadLocalProvider tlp : data.providers) {
tlp.remove();
}
}
}
@Override
public String toString() {
return factory.getClass().getInterfaces()[0].getName();
}
@Override
public int hashCode() {
return Objects.hashCode(factoryKey, collector);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof FactoryProvider2)) {
return false;
}
FactoryProvider2<?> other = (FactoryProvider2<?>) obj;
return factoryKey.equals(other.factoryKey) && Objects.equal(collector, other.collector);
}
/**
* All the data necessary to perform an assisted inject.
*/
private static class AssistData implements AssistedMethod {
/**
* the constructor the implementation is constructed with.
*/
final Constructor<?> constructor;
/**
* the return type in the factory method that the constructor is bound to.
*/
final Key<?> returnType;
/**
* the parameters in the factory method associated with this data.
*/
final ImmutableList<Key<?>> paramTypes;
/**
* the type of the implementation constructed
*/
final TypeLiteral<?> implementationType;
/**
* All non-assisted dependencies required by this method.
*/
final Set<Dependency<?>> dependencies;
/**
* The factory method associated with this data
*/
final Method factoryMethod;
/**
* true if {@link #isValidForOptimizedAssistedInject} returned true.
*/
final boolean optimized;
/**
* the list of optimized providers, empty if not optimized.
*/
final List<ThreadLocalProvider> providers;
/**
* used to perform optimized factory creations.
*/
volatile Binding<?> cachedBinding; // TODO: volatile necessary?
AssistData(Constructor<?> constructor, Key<?> returnType, ImmutableList<Key<?>> paramTypes,
TypeLiteral<?> implementationType, Method factoryMethod,
Set<Dependency<?>> dependencies,
boolean optimized, List<ThreadLocalProvider> providers) {
this.constructor = constructor;
this.returnType = returnType;
this.paramTypes = paramTypes;
this.implementationType = implementationType;
this.factoryMethod = factoryMethod;
this.dependencies = dependencies;
this.optimized = optimized;
this.providers = providers;
}
@Override
public String toString() {
return Objects.toStringHelper(getClass())
.add("ctor", constructor)
.add("return type", returnType)
.add("param type", paramTypes)
.add("implementation type", implementationType)
.add("dependencies", dependencies)
.add("factory method", factoryMethod)
.add("optimized", optimized)
.add("providers", providers)
.add("cached binding", cachedBinding)
.toString();
}
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
public Method getFactoryMethod() {
return factoryMethod;
}
public Constructor<?> getImplementationConstructor() {
return constructor;
}
public TypeLiteral<?> getImplementationType() {
return implementationType;
}
}
// not <T> because we'll never know and this is easier than suppressing warnings.
private static class ThreadLocalProvider extends ThreadLocal<Object> implements Provider<Object> {
@Override
protected Object initialValue() {
throw new IllegalStateException(
"Cannot use optimized @Assisted provider outside the scope of the constructor."
+ " (This should never happen. If it does, please report it.)");
}
}
/**
* Wrapper around MethodHandles/MethodHandle, so we can compile+run on java6.
*/
private static class MethodHandleWrapper {
static final int ALL_MODES = Modifier.PRIVATE
| Modifier.STATIC /* package */
| Modifier.PUBLIC
| Modifier.PROTECTED;
static final Method unreflectSpecial;
static final Method bindTo;
static final Method invokeWithArguments;
static final Constructor<?> lookupCxtor;
static final boolean valid;
static {
Method unreflectSpecialTmp = null;
Method bindToTmp = null;
Method invokeWithArgumentsTmp = null;
boolean validTmp = false;
Constructor<?> lookupCxtorTmp = null;
try {
Class<?> lookupClass = Class.forName("java.lang.invoke.MethodHandles$Lookup");
unreflectSpecialTmp = lookupClass.getMethod("unreflectSpecial", Method.class, Class.class);
Class<?> methodHandleClass = Class.forName("java.lang.invoke.MethodHandle");
bindToTmp = methodHandleClass.getMethod("bindTo", Object.class);
invokeWithArgumentsTmp = methodHandleClass.getMethod("invokeWithArguments", Object[].class);
lookupCxtorTmp = lookupClass.getDeclaredConstructor(Class.class, int.class);
lookupCxtorTmp.setAccessible(true);
validTmp = true;
} catch (Exception invalid) {
// Ignore the exception, store the values & exit early in create(..) if invalid.
}
// Store refs to later.
valid = validTmp;
unreflectSpecial = unreflectSpecialTmp;
bindTo = bindToTmp;
invokeWithArguments = invokeWithArgumentsTmp;
lookupCxtor = lookupCxtorTmp;
}
final Object handle;
MethodHandleWrapper(Object handle) {
this.handle = handle;
}
static MethodHandleWrapper create(Method method, Object proxy) {
if (!valid) {
return null;
}
try {
Class<?> declaringClass = method.getDeclaringClass();
// Note: this isn't a public API, but we need to use it in order to call default methods.
Object lookup = lookupCxtor.newInstance(declaringClass, ALL_MODES);
method.setAccessible(true);
// These are part of the public API, but we use reflection since we run on java6
// and they were introduced in java7.
lookup = unreflectSpecial.invoke(lookup, method, declaringClass);
Object handle = bindTo.invoke(lookup, proxy);
return new MethodHandleWrapper(handle);
} catch (InvocationTargetException ite) {
return null;
} catch (IllegalAccessException iae) {
return null;
} catch (InstantiationException ie) {
return null;
}
}
Object invokeWithArguments(Object[] args) throws Exception {
// We must cast the args to an object so the Object[] is the first param,
// as opposed to each individual varargs param.
return invokeWithArguments.invoke(handle, (Object) args);
}
@Override
public String toString() {
return handle.toString();
}
}
}

View file

@ -0,0 +1,146 @@
package com.google.inject.assistedinject;
import com.google.inject.ConfigurationException;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.internal.Annotations;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import static com.google.common.base.Preconditions.checkArgument;
/**
* Models a method or constructor parameter.
*/
class Parameter {
private final Type type;
private final boolean isAssisted;
private final Annotation bindingAnnotation;
private final boolean isProvider;
private volatile Provider<? extends Object> provider;
public Parameter(Type type, Annotation[] annotations) {
this.type = type;
this.bindingAnnotation = getBindingAnnotation(annotations);
this.isAssisted = hasAssistedAnnotation(annotations);
this.isProvider = isProvider(type);
}
public boolean isProvidedByFactory() {
return isAssisted;
}
public Type getType() {
return type;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
if (isAssisted) {
result.append("@Assisted ");
}
if (bindingAnnotation != null) {
result.append(bindingAnnotation).append(" ");
}
return result.append(type).toString();
}
private boolean hasAssistedAnnotation(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(Assisted.class)) {
return true;
}
}
return false;
}
/**
* Returns the Guice {@link Key} for this parameter.
*/
public Object getValue(Injector injector) {
if (null == provider) {
synchronized (this) {
if (null == provider) {
provider = isProvider
? injector.getProvider(getBindingForType(getProvidedType(type)))
: injector.getProvider(getPrimaryBindingKey());
}
}
}
return isProvider ? provider : provider.get();
}
public boolean isBound(Injector injector) {
return isBound(injector, getPrimaryBindingKey())
|| isBound(injector, fixAnnotations(getPrimaryBindingKey()));
}
private boolean isBound(Injector injector, Key<?> key) {
// This method is particularly lame - we really need an API that can test
// for any binding, implicit or explicit
try {
return injector.getBinding(key) != null;
} catch (ConfigurationException e) {
return false;
}
}
/**
* Replace annotation instances with annotation types, this is only
* appropriate for testing if a key is bound and not for injecting.
*
* See Guice bug 125,
* https://github.com/google/guice/issues/125
*/
public Key<?> fixAnnotations(Key<?> key) {
return key.getAnnotation() == null
? key
: Key.get(key.getTypeLiteral(), key.getAnnotation().annotationType());
}
Key<?> getPrimaryBindingKey() {
return isProvider
? getBindingForType(getProvidedType(type))
: getBindingForType(type);
}
private Type getProvidedType(Type type) {
return ((ParameterizedType) type).getActualTypeArguments()[0];
}
private boolean isProvider(Type type) {
return type instanceof ParameterizedType
&& ((ParameterizedType) type).getRawType() == Provider.class;
}
private Key<?> getBindingForType(Type type) {
return bindingAnnotation != null
? Key.get(type, bindingAnnotation)
: Key.get(type);
}
/**
* Returns the unique binding annotation from the specified list, or
* {@code null} if there are none.
*
* @throws IllegalStateException if multiple binding annotations exist.
*/
private Annotation getBindingAnnotation(Annotation[] annotations) {
Annotation bindingAnnotation = null;
for (Annotation annotation : annotations) {
if (Annotations.isBindingAnnotation(annotation.annotationType())) {
checkArgument(bindingAnnotation == null,
"Parameter has multiple binding annotations: %s and %s", bindingAnnotation, annotation);
bindingAnnotation = annotation;
}
}
return bindingAnnotation;
}
}

View file

@ -0,0 +1,47 @@
package com.google.inject.assistedinject;
import com.google.inject.TypeLiteral;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A list of {@link TypeLiteral}s to match an injectable Constructor's assited
* parameter types to the corresponding factory method.
*/
class ParameterListKey {
private final List<Type> paramList;
public ParameterListKey(List<Type> paramList) {
this.paramList = new ArrayList<Type>(paramList);
}
public ParameterListKey(Type[] types) {
this(Arrays.asList(types));
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof ParameterListKey)) {
return false;
}
ParameterListKey other = (ParameterListKey) o;
return paramList.equals(other.paramList);
}
@Override
public int hashCode() {
return paramList.hashCode();
}
@Override
public String toString() {
return paramList.toString();
}
}

View file

@ -0,0 +1,19 @@
package com.google.inject.multibindings;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Allows {@literal @}{@link ProvidesIntoMap} to specify a class map key.
*/
@MapKey(unwrapValue = true)
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface ClassMapKey {
Class<?> value();
}

View file

@ -0,0 +1,31 @@
package com.google.inject.multibindings;
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* An internal binding annotation applied to each element in a multibinding.
* All elements are assigned a globally-unique id to allow different modules
* to contribute multibindings independently.
*/
@Retention(RUNTIME)
@BindingAnnotation
@interface Element {
String setName();
int uniqueId();
Type type();
String keyType();
enum Type {
MAPBINDER,
MULTIBINDER,
OPTIONALBINDER;
}
}

View file

@ -0,0 +1,167 @@
package com.google.inject.multibindings;
import com.google.common.base.Objects;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Scope;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.BindingScopingVisitor;
import com.google.inject.spi.ConstructorBinding;
import com.google.inject.spi.ConvertedConstantBinding;
import com.google.inject.spi.DefaultBindingTargetVisitor;
import com.google.inject.spi.ExposedBinding;
import com.google.inject.spi.InstanceBinding;
import com.google.inject.spi.LinkedKeyBinding;
import com.google.inject.spi.ProviderBinding;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderKeyBinding;
import com.google.inject.spi.UntargettedBinding;
import java.lang.annotation.Annotation;
/**
* Visits bindings to return a {@code IndexedBinding} that can be used to emulate the binding
* deduplication that Guice internally performs.
*/
class Indexer extends DefaultBindingTargetVisitor<Object, Indexer.IndexedBinding>
implements BindingScopingVisitor<Object> {
private static final Object EAGER_SINGLETON = new Object();
final Injector injector;
Indexer(Injector injector) {
this.injector = injector;
}
boolean isIndexable(Binding<?> binding) {
return binding.getKey().getAnnotation() instanceof Element;
}
private Object scope(Binding<?> binding) {
return binding.acceptScopingVisitor(this);
}
@Override
public IndexedBinding visit(ConstructorBinding<? extends Object> binding) {
return new IndexedBinding(binding, BindingType.CONSTRUCTOR, scope(binding),
binding.getConstructor());
}
@Override
public IndexedBinding visit(
ConvertedConstantBinding<? extends Object> binding) {
return new IndexedBinding(binding, BindingType.CONSTANT, scope(binding),
binding.getValue());
}
@Override
public IndexedBinding visit(ExposedBinding<? extends Object> binding) {
return new IndexedBinding(binding, BindingType.EXPOSED, scope(binding), binding);
}
@Override
public IndexedBinding visit(InstanceBinding<? extends Object> binding) {
return new IndexedBinding(binding, BindingType.INSTANCE, scope(binding),
binding.getInstance());
}
@Override
public IndexedBinding visit(LinkedKeyBinding<? extends Object> binding) {
return new IndexedBinding(binding, BindingType.LINKED_KEY, scope(binding),
binding.getLinkedKey());
}
@Override
public IndexedBinding visit(ProviderBinding<? extends Object> binding) {
return new IndexedBinding(binding, BindingType.PROVIDED_BY, scope(binding),
injector.getBinding(binding.getProvidedKey()));
}
@Override
public IndexedBinding visit(ProviderInstanceBinding<? extends Object> binding) {
return new IndexedBinding(binding, BindingType.PROVIDER_INSTANCE, scope(binding),
binding.getUserSuppliedProvider());
}
@Override
public IndexedBinding visit(ProviderKeyBinding<? extends Object> binding) {
return new IndexedBinding(binding, BindingType.PROVIDER_KEY, scope(binding),
binding.getProviderKey());
}
@Override
public IndexedBinding visit(UntargettedBinding<? extends Object> binding) {
return new IndexedBinding(binding, BindingType.UNTARGETTED, scope(binding), null);
}
@Override
public Object visitEagerSingleton() {
return EAGER_SINGLETON;
}
@Override
public Object visitNoScoping() {
return Scopes.NO_SCOPE;
}
@Override
public Object visitScope(Scope scope) {
return scope;
}
@Override
public Object visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
return scopeAnnotation;
}
enum BindingType {
INSTANCE,
PROVIDER_INSTANCE,
PROVIDER_KEY,
LINKED_KEY,
UNTARGETTED,
CONSTRUCTOR,
CONSTANT,
EXPOSED,
PROVIDED_BY,
}
static class IndexedBinding {
final String annotationName;
final Element.Type annotationType;
final TypeLiteral<?> typeLiteral;
final Object scope;
final BindingType type;
final Object extraEquality;
IndexedBinding(Binding<?> binding, BindingType type, Object scope, Object extraEquality) {
this.scope = scope;
this.type = type;
this.extraEquality = extraEquality;
this.typeLiteral = binding.getKey().getTypeLiteral();
Element annotation = (Element) binding.getKey().getAnnotation();
this.annotationName = annotation.setName();
this.annotationType = annotation.type();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof IndexedBinding)) {
return false;
}
IndexedBinding o = (IndexedBinding) obj;
return type == o.type
&& Objects.equal(scope, o.scope)
&& typeLiteral.equals(o.typeLiteral)
&& annotationType == o.annotationType
&& annotationName.equals(o.annotationName)
&& Objects.equal(extraEquality, o.extraEquality);
}
@Override
public int hashCode() {
return Objects.hashCode(type, scope, typeLiteral, annotationType, annotationName,
extraEquality);
}
}
}

View file

@ -0,0 +1,873 @@
package com.google.inject.multibindings;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Supplier;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
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.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.internal.Errors;
import com.google.inject.multibindings.Indexer.IndexedBinding;
import com.google.inject.multibindings.Multibinder.RealMultibinder;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.Element;
import com.google.inject.spi.HasDependencies;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderLookup;
import com.google.inject.spi.ProviderWithDependencies;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.Toolable;
import com.google.inject.util.Types;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import static com.google.inject.multibindings.Element.Type.MAPBINDER;
import static com.google.inject.multibindings.Multibinder.checkConfiguration;
import static com.google.inject.multibindings.Multibinder.checkNotNull;
import static com.google.inject.multibindings.Multibinder.setOf;
import static com.google.inject.util.Types.newParameterizedType;
import static com.google.inject.util.Types.newParameterizedTypeWithOwner;
/**
* An API to bind multiple map entries separately, only to later inject them as
* a complete map. MapBinder is intended for use in your application's module:
* <pre><code>
* public class SnacksModule extends AbstractModule {
* protected void configure() {
* MapBinder&lt;String, Snack&gt; mapbinder
* = MapBinder.newMapBinder(binder(), String.class, Snack.class);
* mapbinder.addBinding("twix").toInstance(new Twix());
* mapbinder.addBinding("snickers").toProvider(SnickersProvider.class);
* mapbinder.addBinding("skittles").to(Skittles.class);
* }
* }</code></pre>
*
* <p>With this binding, a {@link Map}{@code <String, Snack>} can now be
* injected:
* <pre><code>
* class SnackMachine {
* {@literal @}Inject
* public SnackMachine(Map&lt;String, Snack&gt; snacks) { ... }
* }</code></pre>
*
* <p>In addition to binding {@code Map<K, V>}, a mapbinder will also bind
* {@code Map<K, Provider<V>>} for lazy value provision:
* <pre><code>
* class SnackMachine {
* {@literal @}Inject
* public SnackMachine(Map&lt;String, Provider&lt;Snack&gt;&gt; snackProviders) { ... }
* }</code></pre>
*
* <p>Contributing mapbindings from different modules is supported. For example,
* it is okay to have both {@code CandyModule} and {@code ChipsModule} both
* create their own {@code MapBinder<String, Snack>}, and to each contribute
* bindings to the snacks map. When that map is injected, it will contain
* entries from both modules.
*
* <p>The map's iteration order is consistent with the binding order. This is
* convenient when multiple elements are contributed by the same module because
* that module can order its bindings appropriately. Avoid relying on the
* iteration order of elements contributed by different modules, since there is
* no equivalent mechanism to order modules.
*
* <p>The map is unmodifiable. Elements can only be added to the map by
* configuring the MapBinder. Elements can never be removed from the map.
*
* <p>Values are resolved at map injection time. If a value is bound to a
* provider, that provider's get method will be called each time the map is
* injected (unless the binding is also scoped, or a map of providers is injected).
*
* <p>Annotations are used to create different maps of the same key/value
* type. Each distinct annotation gets its own independent map.
*
* <p><strong>Keys must be distinct.</strong> If the same key is bound more than
* once, map injection will fail. However, use {@link #permitDuplicates()} in
* order to allow duplicate keys; extra bindings to {@code Map<K, Set<V>>} and
* {@code Map<K, Set<Provider<V>>} will be added.
*
* <p><strong>Keys must be non-null.</strong> {@code addBinding(null)} will
* throw an unchecked exception.
*
* <p><strong>Values must be non-null to use map injection.</strong> If any
* value is null, map injection will fail (although injecting a map of providers
* will not).
*/
public abstract class MapBinder<K, V> {
private MapBinder() {
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a
* {@link Map} that is itself bound with no binding annotation.
*/
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
return newRealMapBinder(binder, keyType, valueType, Key.get(mapOf(keyType, valueType)),
Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType)));
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a
* {@link Map} that is itself bound with no binding annotation.
*/
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
Class<K> keyType, Class<V> valueType) {
return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType));
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a
* {@link Map} that is itself bound with {@code annotation}.
*/
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
TypeLiteral<K> keyType, TypeLiteral<V> valueType, Annotation annotation) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
return newRealMapBinder(binder, keyType, valueType,
Key.get(mapOf(keyType, valueType), annotation),
Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType), annotation));
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a
* {@link Map} that is itself bound with {@code annotation}.
*/
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
Class<K> keyType, Class<V> valueType, Annotation annotation) {
return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotation);
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a
* {@link Map} that is itself bound with {@code annotationType}.
*/
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, TypeLiteral<K> keyType,
TypeLiteral<V> valueType, Class<? extends Annotation> annotationType) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
return newRealMapBinder(binder, keyType, valueType,
Key.get(mapOf(keyType, valueType), annotationType),
Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType), annotationType));
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a
* {@link Map} that is itself bound with {@code annotationType}.
*/
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, Class<K> keyType,
Class<V> valueType, Class<? extends Annotation> annotationType) {
return newMapBinder(
binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotationType);
}
@SuppressWarnings("unchecked") // a map of <K, V> is safely a Map<K, V>
static <K, V> TypeLiteral<Map<K, V>> mapOf(
TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
return (TypeLiteral<Map<K, V>>) TypeLiteral.get(
Types.mapOf(keyType.getType(), valueType.getType()));
}
@SuppressWarnings("unchecked") // a provider map <K, V> is safely a Map<K, Provider<V>>
static <K, V> TypeLiteral<Map<K, Provider<V>>> mapOfProviderOf(
TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
return (TypeLiteral<Map<K, Provider<V>>>) TypeLiteral.get(
Types.mapOf(keyType.getType(), Types.providerOf(valueType.getType())));
}
// provider map <K, V> is safely a Map<K, javax.inject.Provider<V>>>
@SuppressWarnings("unchecked")
static <K, V> TypeLiteral<Map<K, javax.inject.Provider<V>>> mapOfJavaxProviderOf(
TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
return (TypeLiteral<Map<K, javax.inject.Provider<V>>>) TypeLiteral.get(
Types.mapOf(keyType.getType(),
newParameterizedType(javax.inject.Provider.class, valueType.getType())));
}
@SuppressWarnings("unchecked") // a provider map <K, Set<V>> is safely a Map<K, Set<Provider<V>>>
static <K, V> TypeLiteral<Map<K, Set<Provider<V>>>> mapOfSetOfProviderOf(
TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
return (TypeLiteral<Map<K, Set<Provider<V>>>>) TypeLiteral.get(
Types.mapOf(keyType.getType(), Types.setOf(Types.providerOf(valueType.getType()))));
}
@SuppressWarnings("unchecked") // a provider entry <K, V> is safely a Map.Entry<K, Provider<V>>
static <K, V> TypeLiteral<Entry<K, Provider<V>>> entryOfProviderOf(
TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
return (TypeLiteral<Entry<K, Provider<V>>>) TypeLiteral.get(newParameterizedTypeWithOwner(
Map.class, Entry.class, keyType.getType(), Types.providerOf(valueType.getType())));
}
// Note: We use valueTypeAndAnnotation effectively as a Pair<TypeLiteral, Annotation|Class>
// since it's an easy way to group a type and an optional annotation type or instance.
static <K, V> RealMapBinder<K, V> newRealMapBinder(Binder binder, TypeLiteral<K> keyType,
Key<V> valueTypeAndAnnotation) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
TypeLiteral<V> valueType = valueTypeAndAnnotation.getTypeLiteral();
return newRealMapBinder(binder, keyType, valueType,
valueTypeAndAnnotation.ofType(mapOf(keyType, valueType)),
Multibinder.newSetBinder(binder,
valueTypeAndAnnotation.ofType(entryOfProviderOf(keyType, valueType))));
}
private static <K, V> RealMapBinder<K, V> newRealMapBinder(Binder binder,
TypeLiteral<K> keyType, TypeLiteral<V> valueType, Key<Map<K, V>> mapKey,
Multibinder<Entry<K, Provider<V>>> entrySetBinder) {
RealMapBinder<K, V> mapBinder =
new RealMapBinder<K, V>(binder, keyType, valueType, mapKey, entrySetBinder);
binder.install(mapBinder);
return mapBinder;
}
/**
* Configures the {@code MapBinder} to handle duplicate entries.
* <p>When multiple equal keys are bound, the value that gets included in the map is
* arbitrary.
* <p>In addition to the {@code Map<K, V>} and {@code Map<K, Provider<V>>}
* maps that are normally bound, a {@code Map<K, Set<V>>} and
* {@code Map<K, Set<Provider<V>>>} are <em>also</em> bound, which contain
* all values bound to each key.
* <p>
* When multiple modules contribute elements to the map, this configuration
* option impacts all of them.
*
* @return this map binder
*/
public abstract MapBinder<K, V> permitDuplicates();
/**
* Returns a binding builder used to add a new entry in the map. Each
* key must be distinct (and non-null). Bound providers will be evaluated each
* time the map is injected.
*
* <p>It is an error to call this method without also calling one of the
* {@code to} methods on the returned binding builder.
*
* <p>Scoping elements independently is supported. Use the {@code in} method
* to specify a binding scope.
*/
public abstract LinkedBindingBuilder<V> addBinding(K key);
/**
* The actual mapbinder plays several roles:
*
* <p>As a MapBinder, it acts as a factory for LinkedBindingBuilders for
* each of the map's values. It delegates to a {@link Multibinder} of
* entries (keys to value providers).
*
* <p>As a Module, it installs the binding to the map itself, as well as to
* a corresponding map whose values are providers. It uses the entry set
* multibinder to construct the map and the provider map.
*
* <p>As a module, this implements equals() and hashcode() in order to trick
* Guice into executing its configure() method only once. That makes it so
* that multiple mapbinders can be created for the same target map, but
* only one is bound. Since the list of bindings is retrieved from the
* injector itself (and not the mapbinder), each mapbinder has access to
* all contributions from all equivalent mapbinders.
*
* <p>Rather than binding a single Map.Entry&lt;K, V&gt;, the map binder
* binds keys and values independently. This allows the values to be properly
* scoped.
*
* <p>We use a subclass to hide 'implements Module' from the public API.
*/
static final class RealMapBinder<K, V> extends MapBinder<K, V> implements Module {
private final TypeLiteral<K> keyType;
private final TypeLiteral<V> valueType;
private final Key<Map<K, V>> mapKey;
private final Key<Map<K, javax.inject.Provider<V>>> javaxProviderMapKey;
private final Key<Map<K, Provider<V>>> providerMapKey;
private final Key<Map<K, Set<V>>> multimapKey;
private final Key<Map<K, Set<Provider<V>>>> providerMultimapKey;
private final RealMultibinder<Entry<K, Provider<V>>> entrySetBinder;
private final Map<K, String> duplicateKeyErrorMessages;
/* the target injector's binder. non-null until initialization, null afterwards */
private Binder binder;
private boolean permitDuplicates;
private ImmutableList<Entry<K, Binding<V>>> mapBindings;
private RealMapBinder(Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType,
Key<Map<K, V>> mapKey, Multibinder<Entry<K, Provider<V>>> entrySetBinder) {
this.keyType = keyType;
this.valueType = valueType;
this.mapKey = mapKey;
this.providerMapKey = mapKey.ofType(mapOfProviderOf(keyType, valueType));
this.javaxProviderMapKey = mapKey.ofType(mapOfJavaxProviderOf(keyType, valueType));
this.multimapKey = mapKey.ofType(mapOf(keyType, setOf(valueType)));
this.providerMultimapKey = mapKey.ofType(mapOfSetOfProviderOf(keyType, valueType));
this.entrySetBinder = (RealMultibinder<Entry<K, Provider<V>>>) entrySetBinder;
this.binder = binder;
this.duplicateKeyErrorMessages = Maps.newHashMap();
}
/**
* Sets the error message to be shown if the key had duplicate non-equal bindings.
*/
void updateDuplicateKeyMessage(K k, String errMsg) {
duplicateKeyErrorMessages.put(k, errMsg);
}
@Override
public MapBinder<K, V> permitDuplicates() {
entrySetBinder.permitDuplicates();
binder.install(new MultimapBinder<K, V>(
multimapKey, providerMultimapKey, entrySetBinder.getSetKey()));
return this;
}
Key<V> getKeyForNewValue(K key) {
checkNotNull(key, "key");
checkConfiguration(!isInitialized(), "MapBinder was already initialized");
Key<V> valueKey = Key.get(valueType,
new RealElement(entrySetBinder.getSetName(), MAPBINDER, keyType.toString()));
entrySetBinder.addBinding().toProvider(new ProviderMapEntry<K, V>(
key, binder.getProvider(valueKey), valueKey));
return valueKey;
}
/**
* This creates two bindings. One for the {@code Map.Entry<K, Provider<V>>}
* and another for {@code V}.
*/
@Override
public LinkedBindingBuilder<V> addBinding(K key) {
return binder.bind(getKeyForNewValue(key));
}
@Override
public void configure(Binder binder) {
checkConfiguration(!isInitialized(), "MapBinder was already initialized");
ImmutableSet<Dependency<?>> dependencies
= ImmutableSet.<Dependency<?>>of(Dependency.get(entrySetBinder.getSetKey()));
// Binds a Map<K, Provider<V>> from a collection of Set<Entry<K, Provider<V>>.
Provider<Set<Entry<K, Provider<V>>>> entrySetProvider = binder
.getProvider(entrySetBinder.getSetKey());
binder.bind(providerMapKey).toProvider(
new RealProviderMapProvider(dependencies, entrySetProvider));
// The map this exposes is internally an ImmutableMap, so it's OK to massage
// the guice Provider to javax Provider in the value (since Guice provider
// implements javax Provider).
@SuppressWarnings("unchecked")
Key massagedProviderMapKey = (Key) providerMapKey;
binder.bind(javaxProviderMapKey).to(massagedProviderMapKey);
Provider<Map<K, Provider<V>>> mapProvider = binder.getProvider(providerMapKey);
binder.bind(mapKey).toProvider(new RealMapProvider(dependencies, mapProvider));
}
boolean containsElement(Element element) {
if (entrySetBinder.containsElement(element)) {
return true;
} else {
Key<?> key;
if (element instanceof Binding) {
key = ((Binding<?>) element).getKey();
} else if (element instanceof ProviderLookup) {
key = ((ProviderLookup<?>) element).getKey();
} else {
return false; // cannot match;
}
return key.equals(mapKey)
|| key.equals(providerMapKey)
|| key.equals(javaxProviderMapKey)
|| key.equals(multimapKey)
|| key.equals(providerMultimapKey)
|| key.equals(entrySetBinder.getSetKey())
|| matchesValueKey(key);
}
}
/**
* Returns true if the key indicates this is a value in the map.
*/
private boolean matchesValueKey(Key<?> key) {
return key.getAnnotation() instanceof RealElement
&& ((RealElement) key.getAnnotation()).setName().equals(entrySetBinder.getSetName())
&& ((RealElement) key.getAnnotation()).type() == MAPBINDER
&& ((RealElement) key.getAnnotation()).keyType().equals(keyType.toString())
&& key.getTypeLiteral().equals(valueType);
}
private boolean isInitialized() {
return binder == null;
}
@Override
public boolean equals(Object o) {
return o instanceof RealMapBinder
&& ((RealMapBinder<?, ?>) o).mapKey.equals(mapKey);
}
@Override
public int hashCode() {
return mapKey.hashCode();
}
private Multimap<K, String> newLinkedKeyArrayValueMultimap() {
return Multimaps.newListMultimap(
new LinkedHashMap<K, Collection<String>>(),
new Supplier<List<String>>() {
@Override
public List<String> get() {
return Lists.newArrayList();
}
});
}
/**
* Binds {@code Map<K, Set<V>>} and {{@code Map<K, Set<Provider<V>>>}.
*/
static final class MultimapBinder<K, V> implements Module {
private final Key<Map<K, Set<V>>> multimapKey;
private final Key<Map<K, Set<Provider<V>>>> providerMultimapKey;
private final Key<Set<Entry<K, Provider<V>>>> entrySetKey;
public MultimapBinder(
Key<Map<K, Set<V>>> multimapKey,
Key<Map<K, Set<Provider<V>>>> providerMultimapKey,
Key<Set<Entry<K, Provider<V>>>> entrySetKey) {
this.multimapKey = multimapKey;
this.providerMultimapKey = providerMultimapKey;
this.entrySetKey = entrySetKey;
}
@Override
public void configure(Binder binder) {
ImmutableSet<Dependency<?>> dependencies
= ImmutableSet.<Dependency<?>>of(Dependency.get(entrySetKey));
Provider<Set<Entry<K, Provider<V>>>> entrySetProvider =
binder.getProvider(entrySetKey);
// Binds a Map<K, Set<Provider<V>>> from a collection of Map<Entry<K, Provider<V>> if
// permitDuplicates was called.
binder.bind(providerMultimapKey).toProvider(
new RealProviderMultimapProvider(dependencies, entrySetProvider));
Provider<Map<K, Set<Provider<V>>>> multimapProvider =
binder.getProvider(providerMultimapKey);
binder.bind(multimapKey).toProvider(
new RealMultimapProvider(dependencies, multimapProvider));
}
@Override
public int hashCode() {
return multimapKey.hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof MultimapBinder
&& ((MultimapBinder<?, ?>) o).multimapKey.equals(multimapKey);
}
final class RealProviderMultimapProvider
extends RealMapBinderProviderWithDependencies<Map<K, Set<Provider<V>>>> {
private final ImmutableSet<Dependency<?>> dependencies;
private final Provider<Set<Entry<K, Provider<V>>>> entrySetProvider;
private Map<K, Set<Provider<V>>> providerMultimap;
private RealProviderMultimapProvider(ImmutableSet<Dependency<?>> dependencies,
Provider<Set<Entry<K, Provider<V>>>> entrySetProvider) {
super(multimapKey);
this.dependencies = dependencies;
this.entrySetProvider = entrySetProvider;
}
@SuppressWarnings("unused")
@Inject
void initialize(Injector injector) {
Map<K, ImmutableSet.Builder<Provider<V>>> providerMultimapMutable =
new LinkedHashMap<K, ImmutableSet.Builder<Provider<V>>>();
for (Entry<K, Provider<V>> entry : entrySetProvider.get()) {
if (!providerMultimapMutable.containsKey(entry.getKey())) {
providerMultimapMutable.put(
entry.getKey(), ImmutableSet.<Provider<V>>builder());
}
providerMultimapMutable.get(entry.getKey()).add(entry.getValue());
}
ImmutableMap.Builder<K, Set<Provider<V>>> providerMultimapBuilder =
ImmutableMap.builder();
for (Entry<K, ImmutableSet.Builder<Provider<V>>> entry
: providerMultimapMutable.entrySet()) {
providerMultimapBuilder.put(entry.getKey(), entry.getValue().build());
}
providerMultimap = providerMultimapBuilder.build();
}
@Override
public Map<K, Set<Provider<V>>> get() {
return providerMultimap;
}
@Override
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
}
final class RealMultimapProvider
extends RealMapBinderProviderWithDependencies<Map<K, Set<V>>> {
private final ImmutableSet<Dependency<?>> dependencies;
private final Provider<Map<K, Set<Provider<V>>>> multimapProvider;
RealMultimapProvider(
ImmutableSet<Dependency<?>> dependencies,
Provider<Map<K, Set<Provider<V>>>> multimapProvider) {
super(multimapKey);
this.dependencies = dependencies;
this.multimapProvider = multimapProvider;
}
@Override
public Map<K, Set<V>> get() {
ImmutableMap.Builder<K, Set<V>> multimapBuilder = ImmutableMap.builder();
for (Entry<K, Set<Provider<V>>> entry : multimapProvider.get().entrySet()) {
K key = entry.getKey();
ImmutableSet.Builder<V> valuesBuilder = ImmutableSet.builder();
for (Provider<V> valueProvider : entry.getValue()) {
V value = valueProvider.get();
checkConfiguration(value != null,
"Multimap injection failed due to null value for key \"%s\"", key);
valuesBuilder.add(value);
}
multimapBuilder.put(key, valuesBuilder.build());
}
return multimapBuilder.build();
}
@Override
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
}
}
static final class ValueProvider<V> implements Provider<V> {
private final Provider<V> delegate;
private final Binding<V> binding;
ValueProvider(Provider<V> delegate, Binding<V> binding) {
this.delegate = delegate;
this.binding = binding;
}
@Override
public V get() {
return delegate.get();
}
public Binding<V> getValueBinding() {
return binding;
}
}
/**
* A Provider that Map.Entry that is also a Provider. The key is the entry in the
* map this corresponds to and the value is the provider of the user's binding.
* This returns itself as the Provider.get value.
*/
static final class ProviderMapEntry<K, V> implements
ProviderWithDependencies<Entry<K, Provider<V>>>, Entry<K, Provider<V>> {
private final K key;
private final Provider<V> provider;
private final Key<V> valueKey;
private ProviderMapEntry(K key, Provider<V> provider, Key<V> valueKey) {
this.key = key;
this.provider = provider;
this.valueKey = valueKey;
}
@Override
public Entry<K, Provider<V>> get() {
return this;
}
@Override
public Set<Dependency<?>> getDependencies() {
return ((HasDependencies) provider).getDependencies();
}
public Key<V> getValueKey() {
return valueKey;
}
@Override
public K getKey() {
return key;
}
@Override
public Provider<V> getValue() {
return provider;
}
@Override
public Provider<V> setValue(Provider<V> value) {
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Entry) {
Entry o = (Entry) obj;
return Objects.equal(key, o.getKey())
&& Objects.equal(provider, o.getValue());
}
return false;
}
@Override
public int hashCode() {
return key.hashCode() ^ provider.hashCode();
}
@Override
public String toString() {
return "ProviderMapEntry(" + key + ", " + provider + ")";
}
}
private static abstract class RealMapWithExtensionProvider<T>
extends RealMapBinderProviderWithDependencies<T>
implements ProviderWithExtensionVisitor<T>, MapBinderBinding<T> {
public RealMapWithExtensionProvider(Object equality) {
super(equality);
}
}
/**
* A base class for ProviderWithDependencies that need equality
* based on a specific object.
*/
private static abstract class RealMapBinderProviderWithDependencies<T> implements ProviderWithDependencies<T> {
private final Object equality;
public RealMapBinderProviderWithDependencies(Object equality) {
this.equality = equality;
}
@Override
public boolean equals(Object obj) {
return this.getClass() == obj.getClass() &&
equality.equals(((RealMapBinderProviderWithDependencies<?>) obj).equality);
}
@Override
public int hashCode() {
return equality.hashCode();
}
}
final class RealProviderMapProvider
extends RealMapBinderProviderWithDependencies<Map<K, Provider<V>>> {
private final ImmutableSet<Dependency<?>> dependencies;
private final Provider<Set<Entry<K, Provider<V>>>> entrySetProvider;
private Map<K, Provider<V>> providerMap;
private RealProviderMapProvider(
ImmutableSet<Dependency<?>> dependencies,
Provider<Set<Entry<K, Provider<V>>>> entrySetProvider) {
super(mapKey);
this.dependencies = dependencies;
this.entrySetProvider = entrySetProvider;
}
@Toolable
@Inject
void initialize(Injector injector) {
RealMapBinder.this.binder = null;
permitDuplicates = entrySetBinder.permitsDuplicates(injector);
Map<K, Provider<V>> providerMapMutable = new LinkedHashMap<K, Provider<V>>();
List<Entry<K, Binding<V>>> bindingsMutable = Lists.newArrayList();
Indexer indexer = new Indexer(injector);
Multimap<K, IndexedBinding> index = HashMultimap.create();
Set<K> duplicateKeys = null;
for (Entry<K, Provider<V>> entry : entrySetProvider.get()) {
ProviderMapEntry<K, V> providerEntry = (ProviderMapEntry<K, V>) entry;
Key<V> valueKey = providerEntry.getValueKey();
Binding<V> valueBinding = injector.getBinding(valueKey);
// If this isn't a dup due to an exact same binding, add it.
if (index.put(providerEntry.getKey(), valueBinding.acceptTargetVisitor(indexer))) {
Provider<V> previous = providerMapMutable.put(providerEntry.getKey(),
new ValueProvider<V>(providerEntry.getValue(), valueBinding));
if (previous != null && !permitDuplicates) {
if (duplicateKeys == null) {
duplicateKeys = Sets.newHashSet();
}
duplicateKeys.add(providerEntry.getKey());
}
bindingsMutable.add(Maps.immutableEntry(providerEntry.getKey(), valueBinding));
}
}
if (duplicateKeys != null) {
// Must use a ListMultimap in case more than one binding has the same source
// and is listed multiple times.
Multimap<K, String> dups = newLinkedKeyArrayValueMultimap();
for (Entry<K, Binding<V>> entry : bindingsMutable) {
if (duplicateKeys.contains(entry.getKey())) {
dups.put(entry.getKey(), "\t at " + Errors.convert(entry.getValue().getSource()));
}
}
StringBuilder sb = new StringBuilder("Map injection failed due to duplicated key ");
boolean first = true;
for (K key : dups.keySet()) {
if (first) {
first = false;
if (duplicateKeyErrorMessages.containsKey(key)) {
sb.setLength(0);
sb.append(duplicateKeyErrorMessages.get(key));
} else {
sb.append("\"" + key + "\", from bindings:\n");
}
} else {
if (duplicateKeyErrorMessages.containsKey(key)) {
sb.append("\n and " + duplicateKeyErrorMessages.get(key));
} else {
sb.append("\n and key: \"" + key + "\", from bindings:\n");
}
}
Joiner.on('\n').appendTo(sb, dups.get(key)).append("\n");
}
checkConfiguration(false, sb.toString());
}
providerMap = ImmutableMap.copyOf(providerMapMutable);
mapBindings = ImmutableList.copyOf(bindingsMutable);
}
@Override
public Map<K, Provider<V>> get() {
return providerMap;
}
@Override
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
}
final class RealMapProvider extends RealMapWithExtensionProvider<Map<K, V>> {
private final ImmutableSet<Dependency<?>> dependencies;
private final Provider<Map<K, Provider<V>>> mapProvider;
private RealMapProvider(
ImmutableSet<Dependency<?>> dependencies,
Provider<Map<K, Provider<V>>> mapProvider) {
super(mapKey);
this.dependencies = dependencies;
this.mapProvider = mapProvider;
}
@Override
public Map<K, V> get() {
// We can initialize the internal table efficiently this way and then swap the values
// one by one.
Map<K, Object> map = new LinkedHashMap<K, Object>(mapProvider.get());
for (Entry<K, Object> entry : map.entrySet()) {
@SuppressWarnings("unchecked") // we initialized the entries with providers
ValueProvider<V> provider = (ValueProvider<V>) entry.getValue();
V value = provider.get();
checkConfiguration(value != null,
"Map injection failed due to null value for key \"%s\", bound at: %s",
entry.getKey(),
provider.getValueBinding().getSource());
entry.setValue(value);
}
@SuppressWarnings("unchecked") // if we exited the loop then we replaced all Providers
Map<K, V> typedMap = (Map<K, V>) map;
return Collections.unmodifiableMap(typedMap);
}
@Override
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
@SuppressWarnings("unchecked")
@Override
public <B, R> R acceptExtensionVisitor(BindingTargetVisitor<B, R> visitor,
ProviderInstanceBinding<? extends B> binding) {
if (visitor instanceof MultibindingsTargetVisitor) {
return ((MultibindingsTargetVisitor<Map<K, V>, R>) visitor).visit(this);
} else {
return visitor.visit(binding);
}
}
@Override
public Key<Map<K, V>> getMapKey() {
return mapKey;
}
@Override
public TypeLiteral<?> getKeyTypeLiteral() {
return keyType;
}
@Override
public TypeLiteral<?> getValueTypeLiteral() {
return valueType;
}
@SuppressWarnings("unchecked")
@Override
public List<Entry<?, Binding<?>>> getEntries() {
if (isInitialized()) {
return (List) mapBindings; // safe because mapBindings is immutable
} else {
throw new UnsupportedOperationException(
"getElements() not supported for module bindings");
}
}
@Override
public boolean permitsDuplicates() {
if (isInitialized()) {
return permitDuplicates;
} else {
throw new UnsupportedOperationException(
"permitsDuplicates() not supported for module bindings");
}
}
@Override
public boolean containsElement(Element element) {
return RealMapBinder.this.containsElement(element);
}
}
}
}

View file

@ -0,0 +1,82 @@
package com.google.inject.multibindings;
import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.Element;
import com.google.inject.spi.Elements;
import java.util.List;
import java.util.Map;
/**
* A binding for a MapBinder.
* <p>
* Although MapBinders may be injected through a variety of generic types (Map&lt;K, V>, Map
* &lt;K, Provider&lt;V>>, Map&lt;K, Set&lt;V>>, Map<K, Set&lt;
* Provider&lt;V>>, and even Set&lt;Map.Entry&lt;K, Provider&lt;V>>), a
* MapBinderBinding exists only on the Binding associated with the Map&lt;K, V> key. Other
* bindings can be validated to be derived from this MapBinderBinding using
* {@link #containsElement(Element)}.
*
* @param <T> The fully qualified type of the map, including Map. For example:
* <code>MapBinderBinding&lt;Map&lt;String, Snack>></code>
*/
public interface MapBinderBinding<T> {
/**
* Returns the {@link Key} for the map.
*/
Key<T> getMapKey();
/**
* Returns the TypeLiteral describing the keys of the map.
* <p>
* The TypeLiteral will always match the type Map's generic type. For example, if getMapKey
* returns a key of <code>Map&lt;String, Snack></code>, then this will always return a
* <code>TypeLiteral&lt;String></code>.
*/
TypeLiteral<?> getKeyTypeLiteral();
/**
* Returns the TypeLiteral describing the values of the map.
* <p>
* The TypeLiteral will always match the type Map's generic type. For example, if getMapKey
* returns a key of <code>Map&lt;String, Snack></code>, then this will always return a
* <code>TypeLiteral&lt;Snack></code>.
*/
TypeLiteral<?> getValueTypeLiteral();
/**
* Returns all entries in the Map. The returned list of Map.Entries contains the key and a binding
* to the value. Duplicate keys or values will exist as separate Map.Entries in the returned list.
* This is only supported on bindings returned from an injector. This will throw
* {@link UnsupportedOperationException} if it is called on an element retrieved from
* {@link Elements#getElements}.
* <p>
* The elements will always match the type Map's generic type. For example, if getMapKey returns a
* key of <code>Map&lt;String, Snack></code>, then this will always return a list of type
* <code>List&lt;Map.Entry&lt;String, Binding&lt;Snack>>></code>.
*/
List<Map.Entry<?, Binding<?>>> getEntries();
/**
* Returns true if the MapBinder permits duplicates. This is only supported on bindings returned
* from an injector. This will throw {@link UnsupportedOperationException} if it is called on a
* MapBinderBinding retrieved from {@link Elements#getElements}.
*/
boolean permitsDuplicates();
/**
* Returns true if this MapBinder contains the given Element in order to build the map or uses the
* given Element in order to support building and injecting the map. This will work for
* MapBinderBindings retrieved from an injector and {@link Elements#getElements}. Usually this is
* only necessary if you are working with elements retrieved from modules (without an Injector),
* otherwise {@link #getEntries} and {@link #permitsDuplicates} are better options.
* <p>
* If you need to introspect the details of the map, such as the keys, values or if it permits
* duplicates, it is necessary to pass the elements through an Injector and use
* {@link #getEntries()} and {@link #permitsDuplicates()}.
*/
boolean containsElement(Element element);
}

View file

@ -0,0 +1,42 @@
package com.google.inject.multibindings;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Allows users define customized key type annotations for map bindings by annotating an annotation
* of a {@code Map}'s key type. The custom key annotation can be applied to methods also annotated
* with {@literal @}{@link ProvidesIntoMap}.
*
* <p>A {@link StringMapKey} and {@link ClassMapKey} are provided for convenience with maps whose
* keys are strings or classes. For maps with enums or primitive types as keys, you must provide
* your own MapKey annotation, such as this one for an enum:
*
* <pre>
* {@literal @}MapKey(unwrapValue = true)
* {@literal @}Retention(RUNTIME)
* public {@literal @}interface MyCustomEnumKey {
* MyCustomEnum value();
* }
* </pre>
*
* You can also use the whole annotation as the key, if {@code unwrapValue=false}.
* When unwrapValue is false, the annotation type will be the key type for the injected map and
* the annotation instances will be the key values. If {@code unwrapValue=true}, the value() type
* will be the key type for injected map and the value() instances will be the keys values.
*/
@Documented
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
public @interface MapKey {
/**
* if {@code unwrapValue} is false, then the whole annotation will be the type and annotation
* instances will be the keys. If {@code unwrapValue} is true, the value() type of key type
* annotation will be the key type for injected map and the value instances will be the keys.
*/
boolean unwrapValue() default true;
}

View file

@ -0,0 +1,575 @@
package com.google.inject.multibindings;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.ConfigurationException;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.internal.Errors;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies;
import com.google.inject.spi.Message;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderWithDependencies;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.Toolable;
import com.google.inject.util.Types;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.primitives.Ints.MAX_POWER_OF_TWO;
import static com.google.inject.multibindings.Element.Type.MULTIBINDER;
import static com.google.inject.name.Names.named;
/**
* An API to bind multiple values separately, only to later inject them as a
* complete collection. Multibinder is intended for use in your application's
* module:
* <pre><code>
* public class SnacksModule extends AbstractModule {
* protected void configure() {
* Multibinder&lt;Snack&gt; multibinder
* = Multibinder.newSetBinder(binder(), Snack.class);
* multibinder.addBinding().toInstance(new Twix());
* multibinder.addBinding().toProvider(SnickersProvider.class);
* multibinder.addBinding().to(Skittles.class);
* }
* }</code></pre>
*
* <p>With this binding, a {@link Set}{@code <Snack>} can now be injected:
* <pre><code>
* class SnackMachine {
* {@literal @}Inject
* public SnackMachine(Set&lt;Snack&gt; snacks) { ... }
* }</code></pre>
*
* If desired, {@link Collection}{@code <Provider<Snack>>} can also be injected.
*
* <p>Contributing multibindings from different modules is supported. For
* example, it is okay for both {@code CandyModule} and {@code ChipsModule}
* to create their own {@code Multibinder<Snack>}, and to each contribute
* bindings to the set of snacks. When that set is injected, it will contain
* elements from both modules.
*
* <p>The set's iteration order is consistent with the binding order. This is
* convenient when multiple elements are contributed by the same module because
* that module can order its bindings appropriately. Avoid relying on the
* iteration order of elements contributed by different modules, since there is
* no equivalent mechanism to order modules.
*
* <p>The set is unmodifiable. Elements can only be added to the set by
* configuring the multibinder. Elements can never be removed from the set.
*
* <p>Elements are resolved at set injection time. If an element is bound to a
* provider, that provider's get method will be called each time the set is
* injected (unless the binding is also scoped).
*
* <p>Annotations are be used to create different sets of the same element
* type. Each distinct annotation gets its own independent collection of
* elements.
*
* <p><strong>Elements must be distinct.</strong> If multiple bound elements
* have the same value, set injection will fail.
*
* <p><strong>Elements must be non-null.</strong> If any set element is null,
* set injection will fail.
*/
public abstract class Multibinder<T> {
private Multibinder() {
}
/**
* Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
* itself bound with no binding annotation.
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type) {
return newRealSetBinder(binder, Key.get(type));
}
/**
* Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
* itself bound with no binding annotation.
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, Class<T> type) {
return newRealSetBinder(binder, Key.get(type));
}
/**
* Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
* itself bound with {@code annotation}.
*/
public static <T> Multibinder<T> newSetBinder(
Binder binder, TypeLiteral<T> type, Annotation annotation) {
return newRealSetBinder(binder, Key.get(type, annotation));
}
/**
* Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
* itself bound with {@code annotation}.
*/
public static <T> Multibinder<T> newSetBinder(
Binder binder, Class<T> type, Annotation annotation) {
return newRealSetBinder(binder, Key.get(type, annotation));
}
/**
* Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
* itself bound with {@code annotationType}.
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type,
Class<? extends Annotation> annotationType) {
return newRealSetBinder(binder, Key.get(type, annotationType));
}
/**
* Returns a new multibinder that collects instances of the key's type in a {@link Set} that is
* itself bound with the annotation (if any) of the key.
*
* @since 4.0
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, Key<T> key) {
return newRealSetBinder(binder, key);
}
/**
* Implementation of newSetBinder.
*/
static <T> RealMultibinder<T> newRealSetBinder(Binder binder, Key<T> key) {
binder = binder.skipSources(RealMultibinder.class, Multibinder.class);
RealMultibinder<T> result = new RealMultibinder<T>(binder, key.getTypeLiteral(),
key.ofType(setOf(key.getTypeLiteral())));
binder.install(result);
return result;
}
/**
* Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
* itself bound with {@code annotationType}.
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, Class<T> type,
Class<? extends Annotation> annotationType) {
return newSetBinder(binder, Key.get(type, annotationType));
}
@SuppressWarnings("unchecked") // wrapping a T in a Set safely returns a Set<T>
static <T> TypeLiteral<Set<T>> setOf(TypeLiteral<T> elementType) {
Type type = Types.setOf(elementType.getType());
return (TypeLiteral<Set<T>>) TypeLiteral.get(type);
}
@SuppressWarnings("unchecked")
static <T> TypeLiteral<Collection<Provider<T>>> collectionOfProvidersOf(
TypeLiteral<T> elementType) {
Type providerType = Types.providerOf(elementType.getType());
Type type = Types.newParameterizedType(Collection.class, providerType);
return (TypeLiteral<Collection<Provider<T>>>) TypeLiteral.get(type);
}
@SuppressWarnings("unchecked")
static <T> TypeLiteral<Collection<javax.inject.Provider<T>>> collectionOfJavaxProvidersOf(
TypeLiteral<T> elementType) {
Type providerType =
Types.newParameterizedType(javax.inject.Provider.class, elementType.getType());
Type type = Types.newParameterizedType(Collection.class, providerType);
return (TypeLiteral<Collection<javax.inject.Provider<T>>>) TypeLiteral.get(type);
}
static void checkConfiguration(boolean condition, String format, Object... args) {
if (condition) {
return;
}
throw new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args))));
}
private static <T> ConfigurationException newDuplicateValuesException(
Map<T, Binding<T>> existingBindings,
Binding<T> binding,
final T newValue,
Binding<T> duplicateBinding) {
T oldValue = getOnlyElement(filter(existingBindings.keySet(), equalTo(newValue)));
String oldString = oldValue.toString();
String newString = newValue.toString();
if (Objects.equal(oldString, newString)) {
// When the value strings match, just show the source of the bindings
return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(
"Set injection failed due to duplicated element \"%s\""
+ "\n Bound at %s\n Bound at %s",
newValue,
duplicateBinding.getSource(),
binding.getSource()))));
} else {
// When the value strings don't match, include them both as they may be useful for debugging
return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(
"Set injection failed due to multiple elements comparing equal:"
+ "\n \"%s\"\n bound at %s"
+ "\n \"%s\"\n bound at %s",
oldValue,
duplicateBinding.getSource(),
newValue,
binding.getSource()))));
}
}
static <T> T checkNotNull(T reference, String name) {
if (reference != null) {
return reference;
}
NullPointerException npe = new NullPointerException(name);
throw new ConfigurationException(ImmutableSet.of(
new Message(npe.toString(), npe)));
}
/**
* Configures the bound set to silently discard duplicate elements. When multiple equal values are
* bound, the one that gets included is arbitrary. When multiple modules contribute elements to
* the set, this configuration option impacts all of them.
*
* @return this multibinder
* @since 3.0
*/
public abstract Multibinder<T> permitDuplicates();
/**
* Returns a binding builder used to add a new element in the set. Each
* bound element must have a distinct value. Bound providers will be
* evaluated each time the set is injected.
*
* <p>It is an error to call this method without also calling one of the
* {@code to} methods on the returned binding builder.
*
* <p>Scoping elements independently is supported. Use the {@code in} method
* to specify a binding scope.
*/
public abstract LinkedBindingBuilder<T> addBinding();
/**
* The actual multibinder plays several roles:
*
* <p>As a Multibinder, it acts as a factory for LinkedBindingBuilders for
* each of the set's elements. Each binding is given an annotation that
* identifies it as a part of this set.
*
* <p>As a Module, it installs the binding to the set itself. As a module,
* this implements equals() and hashcode() in order to trick Guice into
* executing its configure() method only once. That makes it so that
* multiple multibinders can be created for the same target collection, but
* only one is bound. Since the list of bindings is retrieved from the
* injector itself (and not the multibinder), each multibinder has access to
* all contributions from all multibinders.
*
* <p>As a Provider, this constructs the set instances.
*
* <p>We use a subclass to hide 'implements Module, Provider' from the public
* API.
*/
static final class RealMultibinder<T> extends Multibinder<T>
implements Module, ProviderWithExtensionVisitor<Set<T>>, HasDependencies,
MultibinderBinding<Set<T>> {
private final TypeLiteral<T> elementType;
private final String setName;
private final Key<Set<T>> setKey;
private final Key<Collection<Provider<T>>> collectionOfProvidersKey;
private final Key<Collection<javax.inject.Provider<T>>> collectionOfJavaxProvidersKey;
private final Key<Boolean> permitDuplicatesKey;
/* the target injector's binder. non-null until initialization, null afterwards */
private Binder binder;
/* a binding for each element in the set. null until initialization, non-null afterwards */
private ImmutableList<Binding<T>> bindings;
private Set<Dependency<?>> dependencies;
/**
* whether duplicates are allowed. Possibly configured by a different instance
*/
private boolean permitDuplicates;
private RealMultibinder(Binder binder, TypeLiteral<T> elementType, Key<Set<T>> setKey) {
this.binder = checkNotNull(binder, "binder");
this.elementType = checkNotNull(elementType, "elementType");
this.setKey = checkNotNull(setKey, "setKey");
this.collectionOfProvidersKey = setKey.ofType(collectionOfProvidersOf(elementType));
this.collectionOfJavaxProvidersKey = setKey.ofType(collectionOfJavaxProvidersOf(elementType));
this.setName = RealElement.nameOf(setKey);
this.permitDuplicatesKey = Key.get(Boolean.class, named(toString() + " permits duplicates"));
}
// This is forked from com.google.common.collect.Maps.capacity
private static int mapCapacity(int numBindings) {
if (numBindings < 3) {
return numBindings + 1;
} else if (numBindings < MAX_POWER_OF_TWO) {
return (int) (numBindings / 0.75F + 1.0F);
}
return Integer.MAX_VALUE;
}
public void configure(Binder binder) {
checkConfiguration(!isInitialized(), "Multibinder was already initialized");
binder.bind(setKey).toProvider(this);
binder.bind(collectionOfProvidersKey).toProvider(
new RealMultibinderCollectionOfProvidersProvider());
// The collection this exposes is internally an ImmutableList, so it's OK to massage
// the guice Provider to javax Provider in the value (since the guice Provider implements
// javax Provider).
@SuppressWarnings("unchecked")
Key key = (Key) collectionOfProvidersKey;
binder.bind(collectionOfJavaxProvidersKey).to(key);
}
@Override
public Multibinder<T> permitDuplicates() {
binder.install(new PermitDuplicatesModule(permitDuplicatesKey));
return this;
}
Key<T> getKeyForNewItem() {
checkConfiguration(!isInitialized(), "Multibinder was already initialized");
return Key.get(elementType, new RealElement(setName, MULTIBINDER, ""));
}
@Override
public LinkedBindingBuilder<T> addBinding() {
return binder.bind(getKeyForNewItem());
}
/**
* Invoked by Guice at Injector-creation time to prepare providers for each
* element in this set. At this time the set's size is known, but its
* contents are only evaluated when get() is invoked.
*/
@Toolable
@Inject
void initialize(Injector injector) {
List<Binding<T>> bindings = Lists.newArrayList();
Set<Indexer.IndexedBinding> index = Sets.newHashSet();
Indexer indexer = new Indexer(injector);
List<Dependency<?>> dependencies = Lists.newArrayList();
for (Binding<?> entry : injector.findBindingsByType(elementType)) {
if (keyMatches(entry.getKey())) {
@SuppressWarnings("unchecked") // protected by findBindingsByType()
Binding<T> binding = (Binding<T>) entry;
if (index.add(binding.acceptTargetVisitor(indexer))) {
bindings.add(binding);
dependencies.add(Dependency.get(binding.getKey()));
}
}
}
this.bindings = ImmutableList.copyOf(bindings);
this.dependencies = ImmutableSet.copyOf(dependencies);
this.permitDuplicates = permitsDuplicates(injector);
this.binder = null;
}
boolean permitsDuplicates(Injector injector) {
return injector.getBindings().containsKey(permitDuplicatesKey);
}
private boolean keyMatches(Key<?> key) {
return key.getTypeLiteral().equals(elementType)
&& key.getAnnotation() instanceof Element
&& ((Element) key.getAnnotation()).setName().equals(setName)
&& ((Element) key.getAnnotation()).type() == MULTIBINDER;
}
private boolean isInitialized() {
return binder == null;
}
public Set<T> get() {
checkConfiguration(isInitialized(), "Multibinder is not initialized");
Map<T, Binding<T>> result = new LinkedHashMap<T, Binding<T>>(mapCapacity(bindings.size()));
for (Binding<T> binding : bindings) {
final T newValue = binding.getProvider().get();
checkConfiguration(newValue != null,
"Set injection failed due to null element bound at: %s",
binding.getSource());
Binding<T> duplicateBinding = result.put(newValue, binding);
if (!permitDuplicates && duplicateBinding != null) {
throw newDuplicateValuesException(result, binding, newValue, duplicateBinding);
}
}
return ImmutableSet.copyOf(result.keySet());
}
@SuppressWarnings("unchecked")
public <B, V> V acceptExtensionVisitor(
BindingTargetVisitor<B, V> visitor,
ProviderInstanceBinding<? extends B> binding) {
if (visitor instanceof MultibindingsTargetVisitor) {
return ((MultibindingsTargetVisitor<Set<T>, V>) visitor).visit(this);
} else {
return visitor.visit(binding);
}
}
String getSetName() {
return setName;
}
public TypeLiteral<?> getElementTypeLiteral() {
return elementType;
}
public Key<Set<T>> getSetKey() {
return setKey;
}
@SuppressWarnings("unchecked")
public List<Binding<?>> getElements() {
if (isInitialized()) {
return (List<Binding<?>>) (List<?>) bindings; // safe because bindings is immutable.
} else {
throw new UnsupportedOperationException("getElements() not supported for module bindings");
}
}
public boolean permitsDuplicates() {
if (isInitialized()) {
return permitDuplicates;
} else {
throw new UnsupportedOperationException(
"permitsDuplicates() not supported for module bindings");
}
}
public boolean containsElement(com.google.inject.spi.Element element) {
if (element instanceof Binding) {
Binding<?> binding = (Binding<?>) element;
return keyMatches(binding.getKey())
|| binding.getKey().equals(permitDuplicatesKey)
|| binding.getKey().equals(setKey)
|| binding.getKey().equals(collectionOfProvidersKey)
|| binding.getKey().equals(collectionOfJavaxProvidersKey);
} else {
return false;
}
}
public Set<Dependency<?>> getDependencies() {
if (!isInitialized()) {
return ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class)));
} else {
return dependencies;
}
}
@Override
public boolean equals(Object o) {
return o instanceof RealMultibinder
&& ((RealMultibinder<?>) o).setKey.equals(setKey);
}
@Override
public int hashCode() {
return setKey.hashCode();
}
@Override
public String toString() {
return (setName.isEmpty() ? "" : setName + " ") + "Multibinder<" + elementType + ">";
}
final class RealMultibinderCollectionOfProvidersProvider
implements ProviderWithDependencies<Collection<Provider<T>>> {
@Override
public Collection<Provider<T>> get() {
checkConfiguration(isInitialized(), "Multibinder is not initialized");
int size = bindings.size();
@SuppressWarnings("unchecked") // safe because we only put Provider<T> into it.
Provider<T>[] providers = new Provider[size];
for (int i = 0; i < size; i++) {
providers[i] = bindings.get(i).getProvider();
}
return ImmutableList.copyOf(providers);
}
@Override
public Set<Dependency<?>> getDependencies() {
if (!isInitialized()) {
return ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class)));
}
ImmutableSet.Builder<Dependency<?>> setBuilder = ImmutableSet.builder();
for (Dependency<?> dependency : dependencies) {
Key key = dependency.getKey();
setBuilder.add(
Dependency.get(key.ofType(Types.providerOf(key.getTypeLiteral().getType()))));
}
return setBuilder.build();
}
Key getCollectionKey() {
return RealMultibinder.this.collectionOfProvidersKey;
}
@Override
public boolean equals(Object o) {
return o instanceof RealMultibinder.RealMultibinderCollectionOfProvidersProvider
&& ((RealMultibinderCollectionOfProvidersProvider) o)
.getCollectionKey().equals(getCollectionKey());
}
@Override
public int hashCode() {
return getCollectionKey().hashCode();
}
}
}
/**
* We install the permit duplicates configuration as its own binding, all by itself. This way,
* if only one of a multibinder's users remember to call permitDuplicates(), they're still
* permitted.
*/
private static class PermitDuplicatesModule extends AbstractModule {
private final Key<Boolean> key;
PermitDuplicatesModule(Key<Boolean> key) {
this.key = key;
}
@Override
protected void configure() {
bind(key).toInstance(true);
}
@Override
public boolean equals(Object o) {
return o instanceof PermitDuplicatesModule
&& ((PermitDuplicatesModule) o).key.equals(key);
}
@Override
public int hashCode() {
return getClass().hashCode() ^ key.hashCode();
}
}
}

View file

@ -0,0 +1,63 @@
package com.google.inject.multibindings;
import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.Element;
import com.google.inject.spi.Elements;
import java.util.List;
/**
* A binding for a Multibinder.
*
* @param <T> The fully qualified type of the set, including Set. For example:
* <code>MultibinderBinding&lt;Set&lt;Boolean>></code>
*/
public interface MultibinderBinding<T> {
/**
* Returns the key for the set.
*/
Key<T> getSetKey();
/**
* Returns the TypeLiteral that describes the type of elements in the set.
* <p>
* The elements will always match the type Set's generic type. For example, if getSetKey returns a
* key of <code>Set&lt;String></code>, then this will always return a
* <code>TypeLiteral&lt;String></code>.
*/
TypeLiteral<?> getElementTypeLiteral();
/**
* Returns all bindings that make up the set. This is only supported on bindings returned from an
* injector. This will throw {@link UnsupportedOperationException} if it is called on an element
* retrieved from {@link Elements#getElements}.
* <p>
* The elements will always match the type Set's generic type. For example, if getSetKey returns a
* key of <code>Set&lt;String></code>, then this will always return a list of type
* <code>List&lt;Binding&lt;String>></code>.
*/
List<Binding<?>> getElements();
/**
* Returns true if the multibinder permits duplicates. This is only supported on bindings returned
* from an injector. This will throw {@link UnsupportedOperationException} if it is called on a
* MultibinderBinding retrieved from {@link Elements#getElements}.
*/
boolean permitsDuplicates();
/**
* Returns true if this Multibinder uses the given Element. This will be true for bindings that
* derive the elements of the set and other bindings that Multibinder uses internally. This will
* work for MultibinderBindings retrieved from an injector and {@link Elements#getElements}.
* Usually this is only necessary if you are working with elements retrieved from modules (without
* an Injector), otherwise {@link #getElements} and {@link #permitsDuplicates} are better options.
* <p>
* If you need to introspect the details of the set, such as the values or if it permits
* duplicates, it is necessary to pass the elements through an Injector and use
* {@link #getElements()} and {@link #permitsDuplicates()}.
*/
boolean containsElement(Element element);
}

View file

@ -0,0 +1,179 @@
package com.google.inject.multibindings;
import com.google.common.collect.ImmutableSet;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.ModuleAnnotatedMethodScanner;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
/**
* Scans a module for annotations that signal multibindings, mapbindings, and optional bindings.
*/
public class MultibindingsScanner {
private MultibindingsScanner() {
}
/**
* Returns a module that, when installed, will scan all modules for methods with the annotations
* {@literal @}{@link ProvidesIntoMap}, {@literal @}{@link ProvidesIntoSet}, and
* {@literal @}{@link ProvidesIntoOptional}.
*
* <p>This is a convenience method, equivalent to doing
* {@code binder().scanModulesForAnnotatedMethods(MultibindingsScanner.scanner())}.
*/
public static Module asModule() {
return new AbstractModule() {
@Override
protected void configure() {
binder().scanModulesForAnnotatedMethods(Scanner.INSTANCE);
}
};
}
/**
* Returns a {@link ModuleAnnotatedMethodScanner} that, when bound, will scan all modules for
* methods with the annotations {@literal @}{@link ProvidesIntoMap},
* {@literal @}{@link ProvidesIntoSet}, and {@literal @}{@link ProvidesIntoOptional}.
*/
public static ModuleAnnotatedMethodScanner scanner() {
return Scanner.INSTANCE;
}
private static AnnotationOrError findMapKeyAnnotation(Binder binder, Method method) {
Annotation foundAnnotation = null;
for (Annotation annotation : method.getAnnotations()) {
MapKey mapKey = annotation.annotationType().getAnnotation(MapKey.class);
if (mapKey != null) {
if (foundAnnotation != null) {
binder.addError("Found more than one MapKey annotations on %s.", method);
return AnnotationOrError.forError();
}
if (mapKey.unwrapValue()) {
try {
// validate there's a declared method called "value"
Method valueMethod = annotation.annotationType().getDeclaredMethod("value");
if (valueMethod.getReturnType().isArray()) {
binder.addError("Array types are not allowed in a MapKey with unwrapValue=true: %s",
annotation.annotationType());
return AnnotationOrError.forError();
}
} catch (NoSuchMethodException invalid) {
binder.addError("No 'value' method in MapKey with unwrapValue=true: %s",
annotation.annotationType());
return AnnotationOrError.forError();
}
}
foundAnnotation = annotation;
}
}
return AnnotationOrError.forPossiblyNullAnnotation(foundAnnotation);
}
@SuppressWarnings({"unchecked", "rawtypes"})
static TypeAndValue<?> typeAndValueOfMapKey(Annotation mapKeyAnnotation) {
if (!mapKeyAnnotation.annotationType().getAnnotation(MapKey.class).unwrapValue()) {
return new TypeAndValue(TypeLiteral.get(mapKeyAnnotation.annotationType()), mapKeyAnnotation);
} else {
try {
Method valueMethod = mapKeyAnnotation.annotationType().getDeclaredMethod("value");
valueMethod.setAccessible(true);
TypeLiteral<?> returnType =
TypeLiteral.get(mapKeyAnnotation.annotationType()).getReturnType(valueMethod);
return new TypeAndValue(returnType, valueMethod.invoke(mapKeyAnnotation));
} catch (NoSuchMethodException e) {
throw new IllegalStateException(e);
} catch (SecurityException e) {
throw new IllegalStateException(e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
} catch (InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
}
private static class Scanner extends ModuleAnnotatedMethodScanner {
private static final Scanner INSTANCE = new Scanner();
@Override
public Set<? extends Class<? extends Annotation>> annotationClasses() {
return ImmutableSet.of(
ProvidesIntoSet.class, ProvidesIntoMap.class, ProvidesIntoOptional.class);
}
@SuppressWarnings({"unchecked", "rawtypes"}) // mapKey doesn't know its key type
@Override
public <T> Key<T> prepareMethod(Binder binder, Annotation annotation, Key<T> key,
InjectionPoint injectionPoint) {
Method method = (Method) injectionPoint.getMember();
AnnotationOrError mapKey = findMapKeyAnnotation(binder, method);
if (annotation instanceof ProvidesIntoSet) {
if (mapKey.annotation != null) {
binder.addError("Found a MapKey annotation on non map binding at %s.", method);
}
return Multibinder.newRealSetBinder(binder, key).getKeyForNewItem();
} else if (annotation instanceof ProvidesIntoMap) {
if (mapKey.error) {
// Already failed on the MapKey, don't bother doing more work.
return key;
}
if (mapKey.annotation == null) {
// If no MapKey, make an error and abort.
binder.addError("No MapKey found for map binding at %s.", method);
return key;
}
TypeAndValue typeAndValue = typeAndValueOfMapKey(mapKey.annotation);
return MapBinder.newRealMapBinder(binder, typeAndValue.type, key)
.getKeyForNewValue(typeAndValue.value);
} else if (annotation instanceof ProvidesIntoOptional) {
if (mapKey.annotation != null) {
binder.addError("Found a MapKey annotation on non map binding at %s.", method);
}
switch (((ProvidesIntoOptional) annotation).value()) {
case DEFAULT:
return OptionalBinder.newRealOptionalBinder(binder, key).getKeyForDefaultBinding();
case ACTUAL:
return OptionalBinder.newRealOptionalBinder(binder, key).getKeyForActualBinding();
}
}
throw new IllegalStateException("Invalid annotation: " + annotation);
}
}
private static class AnnotationOrError {
final Annotation annotation;
final boolean error;
AnnotationOrError(Annotation annotation, boolean error) {
this.annotation = annotation;
this.error = error;
}
static AnnotationOrError forPossiblyNullAnnotation(Annotation annotation) {
return new AnnotationOrError(annotation, false);
}
static AnnotationOrError forError() {
return new AnnotationOrError(null, true);
}
}
private static class TypeAndValue<T> {
final TypeLiteral<T> type;
final T value;
TypeAndValue(TypeLiteral<T> type, T value) {
this.type = type;
this.value = value;
}
}
}

View file

@ -0,0 +1,31 @@
package com.google.inject.multibindings;
import com.google.inject.spi.BindingTargetVisitor;
/**
* A visitor for the multibinder extension.
* <p>
* If your {@link BindingTargetVisitor} implements this interface, bindings created by using
* {@link Multibinder}, {@link MapBinder} or {@link OptionalBinderBinding} will be visited through
* this interface.
*/
public interface MultibindingsTargetVisitor<T, V> extends BindingTargetVisitor<T, V> {
/**
* Visits a binding created through {@link Multibinder}.
*/
V visit(MultibinderBinding<? extends T> multibinding);
/**
* Visits a binding created through {@link MapBinder}.
*/
V visit(MapBinderBinding<? extends T> mapbinding);
/**
* Visits a binding created through {@link OptionalBinder}.
*
* @since 4.0
*/
V visit(OptionalBinderBinding<? extends T> optionalbinding);
}

View file

@ -0,0 +1,779 @@
package com.google.inject.multibindings;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.Element;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderLookup;
import com.google.inject.spi.ProviderWithDependencies;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.Toolable;
import com.google.inject.util.Types;
import javax.inject.Qualifier;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.inject.multibindings.Multibinder.checkConfiguration;
import static com.google.inject.util.Types.newParameterizedType;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* An API to bind optional values, optionally with a default value.
* OptionalBinder fulfills two roles: <ol>
* <li>It allows a framework to define an injection point that may or
* may not be bound by users.
* <li>It allows a framework to supply a default value that can be changed
* by users.
* </ol>
*
* <p>When an OptionalBinder is added, it will always supply the bindings:
* {@code Optional<T>} and {@code Optional<Provider<T>>}. If
* {@link #setBinding} or {@link #setDefault} are called, it will also
* bind {@code T}.
*
* <p>{@code setDefault} is intended for use by frameworks that need a default
* value. User code can call {@code setBinding} to override the default.
* <b>Warning: Even if setBinding is called, the default binding
* will still exist in the object graph. If it is a singleton, it will be
* instantiated in {@code Stage.PRODUCTION}.</b>
*
* <p>If setDefault or setBinding are linked to Providers, the Provider may return
* {@code null}. If it does, the Optional bindings will be absent. Binding
* setBinding to a Provider that returns null will not cause OptionalBinder
* to fall back to the setDefault binding.
*
* <p>If neither setDefault nor setBinding are called, it will try to link to a
* user-supplied binding of the same type. If no binding exists, the optionals
* will be absent. Otherwise, if a user-supplied binding of that type exists,
* or if setBinding or setDefault are called, the optionals will return present
* if they are bound to a non-null value.
*
* <p>Values are resolved at injection time. If a value is bound to a
* provider, that provider's get method will be called each time the optional
* is injected (unless the binding is also scoped, or an optional of provider is
* injected).
*
* <p>Annotations are used to create different optionals of the same key/value
* type. Each distinct annotation gets its own independent binding.
*
* <pre><code>
* public class FrameworkModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Renamer.class);
* }
* }</code></pre>
*
* <p>With this module, an {@link Optional}{@code <Renamer>} can now be
* injected. With no other bindings, the optional will be absent.
* Users can specify bindings in one of two ways:
*
* <p>Option 1:
* <pre><code>
* public class UserRenamerModule extends AbstractModule {
* protected void configure() {
* bind(Renamer.class).to(ReplacingRenamer.class);
* }
* }</code></pre>
*
* <p>or Option 2:
* <pre><code>
* public class UserRenamerModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Renamer.class)
* .setBinding().to(ReplacingRenamer.class);
* }
* }</code></pre>
* With both options, the {@code Optional<Renamer>} will be present and supply the
* ReplacingRenamer.
*
* <p>Default values can be supplied using:
* <pre><code>
* public class FrameworkModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
* .setDefault().toInstance(DEFAULT_LOOKUP_URL);
* }
* }</code></pre>
* With the above module, code can inject an {@code @LookupUrl String} and it
* will supply the DEFAULT_LOOKUP_URL. A user can change this value by binding
* <pre><code>
* public class UserLookupModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
* .setBinding().toInstance(CUSTOM_LOOKUP_URL);
* }
* }</code></pre>
* ... which will override the default value.
*
* <p>If one module uses setDefault the only way to override the default is to use setBinding.
* It is an error for a user to specify the binding without using OptionalBinder if
* setDefault or setBinding are called. For example,
* <pre><code>
* public class FrameworkModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
* .setDefault().toInstance(DEFAULT_LOOKUP_URL);
* }
* }
* public class UserLookupModule extends AbstractModule {
* protected void configure() {
* bind(Key.get(String.class, LookupUrl.class)).toInstance(CUSTOM_LOOKUP_URL);
* }
* }</code></pre>
* ... would generate an error, because both the framework and the user are trying to bind
* {@code @LookupUrl String}.
*/
public abstract class OptionalBinder<T> {
/* Reflectively capture java 8's Optional types so we can bind them if we're running in java8. */
private static final Class<?> JAVA_OPTIONAL_CLASS;
private static final Method JAVA_EMPTY_METHOD;
private static final Method JAVA_OF_NULLABLE_METHOD;
static {
Class<?> optional = null;
Method empty = null;
Method ofNullable = null;
boolean useJavaOptional = false;
try {
optional = Class.forName("java.util.Optional");
empty = optional.getDeclaredMethod("empty");
ofNullable = optional.getDeclaredMethod("ofNullable", Object.class);
useJavaOptional = true;
} catch (ClassNotFoundException ignored) {
} catch (NoSuchMethodException ignored) {
} catch (SecurityException ignored) {
}
JAVA_OPTIONAL_CLASS = useJavaOptional ? optional : null;
JAVA_EMPTY_METHOD = useJavaOptional ? empty : null;
JAVA_OF_NULLABLE_METHOD = useJavaOptional ? ofNullable : null;
}
private OptionalBinder() {
}
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, Class<T> type) {
return newRealOptionalBinder(binder, Key.get(type));
}
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, TypeLiteral<T> type) {
return newRealOptionalBinder(binder, Key.get(type));
}
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, Key<T> type) {
return newRealOptionalBinder(binder, type);
}
static <T> RealOptionalBinder<T> newRealOptionalBinder(Binder binder, Key<T> type) {
binder = binder.skipSources(OptionalBinder.class, RealOptionalBinder.class);
RealOptionalBinder<T> optionalBinder = new RealOptionalBinder<T>(binder, type);
binder.install(optionalBinder);
return optionalBinder;
}
@SuppressWarnings("unchecked")
static <T> TypeLiteral<Optional<T>> optionalOf(
TypeLiteral<T> type) {
return (TypeLiteral<Optional<T>>) TypeLiteral.get(
Types.newParameterizedType(Optional.class, type.getType()));
}
static <T> TypeLiteral<?> javaOptionalOf(
TypeLiteral<T> type) {
checkState(JAVA_OPTIONAL_CLASS != null, "java.util.Optional not found");
return TypeLiteral.get(Types.newParameterizedType(JAVA_OPTIONAL_CLASS, type.getType()));
}
@SuppressWarnings("unchecked")
static <T> TypeLiteral<Optional<javax.inject.Provider<T>>> optionalOfJavaxProvider(
TypeLiteral<T> type) {
return (TypeLiteral<Optional<javax.inject.Provider<T>>>) TypeLiteral.get(
Types.newParameterizedType(Optional.class,
newParameterizedType(javax.inject.Provider.class, type.getType())));
}
static <T> TypeLiteral<?> javaOptionalOfJavaxProvider(
TypeLiteral<T> type) {
checkState(JAVA_OPTIONAL_CLASS != null, "java.util.Optional not found");
return TypeLiteral.get(Types.newParameterizedType(JAVA_OPTIONAL_CLASS,
newParameterizedType(javax.inject.Provider.class, type.getType())));
}
@SuppressWarnings("unchecked")
static <T> TypeLiteral<Optional<Provider<T>>> optionalOfProvider(TypeLiteral<T> type) {
return (TypeLiteral<Optional<Provider<T>>>) TypeLiteral.get(Types.newParameterizedType(
Optional.class, newParameterizedType(Provider.class, type.getType())));
}
static <T> TypeLiteral<?> javaOptionalOfProvider(TypeLiteral<T> type) {
checkState(JAVA_OPTIONAL_CLASS != null, "java.util.Optional not found");
return TypeLiteral.get(Types.newParameterizedType(JAVA_OPTIONAL_CLASS,
newParameterizedType(Provider.class, type.getType())));
}
@SuppressWarnings("unchecked")
static <T> Key<Provider<T>> providerOf(Key<T> key) {
Type providerT = Types.providerOf(key.getTypeLiteral().getType());
return (Key<Provider<T>>) key.ofType(providerT);
}
/**
* Returns a binding builder used to set the default value that will be injected.
* The binding set by this method will be ignored if {@link #setBinding} is called.
*
* <p>It is an error to call this method without also calling one of the {@code to}
* methods on the returned binding builder.
*/
public abstract LinkedBindingBuilder<T> setDefault();
/**
* Returns a binding builder used to set the actual value that will be injected.
* This overrides any binding set by {@link #setDefault}.
*
* <p>It is an error to call this method without also calling one of the {@code to}
* methods on the returned binding builder.
*/
public abstract LinkedBindingBuilder<T> setBinding();
enum Source {DEFAULT, ACTUAL}
@Retention(RUNTIME)
@Qualifier
@interface Default {
String value();
}
@Retention(RUNTIME)
@Qualifier
@interface Actual {
String value();
}
/**
* The actual OptionalBinder plays several roles. It implements Module to hide that
* fact from the public API, and installs the various bindings that are exposed to the user.
*/
static final class RealOptionalBinder<T> extends OptionalBinder<T> implements Module {
private final Key<T> typeKey;
private final Key<Optional<T>> optionalKey;
private final Key<Optional<javax.inject.Provider<T>>> optionalJavaxProviderKey;
private final Key<Optional<Provider<T>>> optionalProviderKey;
private final Provider<Optional<Provider<T>>> optionalProviderT;
private final Key<T> defaultKey;
private final Key<T> actualKey;
private final Key javaOptionalKey;
private final Key javaOptionalJavaxProviderKey;
private final Key javaOptionalProviderKey;
/**
* the target injector's binder. non-null until initialization, null afterwards
*/
private Binder binder;
/**
* the default binding, for the SPI.
*/
private Binding<T> defaultBinding;
/**
* the actual binding, for the SPI
*/
private Binding<T> actualBinding;
/**
* the dependencies -- initialized with defaults & overridden when tooled.
*/
private Set<Dependency<?>> dependencies;
/**
* the dependencies -- initialized with defaults & overridden when tooled.
*/
private Set<Dependency<?>> providerDependencies;
private RealOptionalBinder(Binder binder, Key<T> typeKey) {
this.binder = binder;
this.typeKey = checkNotNull(typeKey);
TypeLiteral<T> literal = typeKey.getTypeLiteral();
this.optionalKey = typeKey.ofType(optionalOf(literal));
this.optionalJavaxProviderKey = typeKey.ofType(optionalOfJavaxProvider(literal));
this.optionalProviderKey = typeKey.ofType(optionalOfProvider(literal));
this.optionalProviderT = binder.getProvider(optionalProviderKey);
String name = RealElement.nameOf(typeKey);
this.defaultKey = Key.get(typeKey.getTypeLiteral(), new DefaultImpl(name));
this.actualKey = Key.get(typeKey.getTypeLiteral(), new ActualImpl(name));
// Until the injector initializes us, we don't know what our dependencies are,
// so initialize to the whole Injector (like Multibinder, and MapBinder indirectly).
this.dependencies = ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class)));
this.providerDependencies =
ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class)));
if (JAVA_OPTIONAL_CLASS != null) {
this.javaOptionalKey = typeKey.ofType(javaOptionalOf(literal));
this.javaOptionalJavaxProviderKey = typeKey.ofType(javaOptionalOfJavaxProvider(literal));
this.javaOptionalProviderKey = typeKey.ofType(javaOptionalOfProvider(literal));
} else {
this.javaOptionalKey = null;
this.javaOptionalJavaxProviderKey = null;
this.javaOptionalProviderKey = null;
}
}
/**
* Adds a binding for T. Multiple calls to this are safe, and will be collapsed as duplicate
* bindings.
*/
private void addDirectTypeBinding(Binder binder) {
binder.bind(typeKey).toProvider(new RealDirectTypeProvider());
}
Key<T> getKeyForDefaultBinding() {
checkConfiguration(!isInitialized(), "already initialized");
addDirectTypeBinding(binder);
return defaultKey;
}
@Override
public LinkedBindingBuilder<T> setDefault() {
return binder.bind(getKeyForDefaultBinding());
}
Key<T> getKeyForActualBinding() {
checkConfiguration(!isInitialized(), "already initialized");
addDirectTypeBinding(binder);
return actualKey;
}
@Override
public LinkedBindingBuilder<T> setBinding() {
return binder.bind(getKeyForActualBinding());
}
@Override
public void configure(Binder binder) {
checkConfiguration(!isInitialized(), "OptionalBinder was already initialized");
binder.bind(optionalProviderKey).toProvider(new RealOptionalProviderProvider());
// Optional is immutable, so it's safe to expose Optional<Provider<T>> as
// Optional<javax.inject.Provider<T>> (since Guice provider implements javax Provider).
@SuppressWarnings({"unchecked", "cast"})
Key massagedOptionalProviderKey = (Key) optionalProviderKey;
binder.bind(optionalJavaxProviderKey).to(massagedOptionalProviderKey);
binder.bind(optionalKey).toProvider(new RealOptionalKeyProvider());
// Bind the java-8 types if we know them.
bindJava8Optional(binder);
}
@SuppressWarnings("unchecked")
private void bindJava8Optional(Binder binder) {
if (JAVA_OPTIONAL_CLASS != null) {
binder.bind(javaOptionalKey).toProvider(new JavaOptionalProvider());
binder.bind(javaOptionalProviderKey).toProvider(new JavaOptionalProviderProvider());
// for the javax version we reuse the guice version since they're type-compatible.
binder.bind(javaOptionalJavaxProviderKey).to(javaOptionalProviderKey);
}
}
private Binding<?> getActualBinding() {
if (isInitialized()) {
return actualBinding;
} else {
throw new UnsupportedOperationException(
"getActualBinding() not supported from Elements.getElements, requires an Injector.");
}
}
private Binding<?> getDefaultBinding() {
if (isInitialized()) {
return defaultBinding;
} else {
throw new UnsupportedOperationException(
"getDefaultBinding() not supported from Elements.getElements, requires an Injector.");
}
}
private boolean containsElement(Element element) {
Key<?> elementKey;
if (element instanceof Binding) {
elementKey = ((Binding<?>) element).getKey();
} else if (element instanceof ProviderLookup) {
elementKey = ((ProviderLookup<?>) element).getKey();
} else {
return false; // cannot match;
}
return elementKey.equals(optionalKey)
|| elementKey.equals(optionalProviderKey)
|| elementKey.equals(optionalJavaxProviderKey)
|| elementKey.equals(defaultKey)
|| elementKey.equals(actualKey)
|| matchesJ8Keys(elementKey)
|| matchesTypeKey(element, elementKey);
}
private boolean matchesJ8Keys(Key<?> elementKey) {
if (JAVA_OPTIONAL_CLASS != null) {
return elementKey.equals(javaOptionalKey)
|| elementKey.equals(javaOptionalProviderKey)
|| elementKey.equals(javaOptionalJavaxProviderKey);
}
return false;
}
/**
* Returns true if the key & element indicate they were bound by this OptionalBinder.
*/
private boolean matchesTypeKey(Element element, Key<?> elementKey) {
// Just doing .equals(typeKey) isn't enough, because the user can bind that themselves.
return elementKey.equals(typeKey)
&& element instanceof ProviderInstanceBinding
&& (((ProviderInstanceBinding) element)
.getUserSuppliedProvider() instanceof RealOptionalBinderProviderWithDependencies);
}
private boolean isInitialized() {
return binder == null;
}
@Override
public boolean equals(Object o) {
return o instanceof RealOptionalBinder
&& ((RealOptionalBinder<?>) o).typeKey.equals(typeKey);
}
@Override
public int hashCode() {
return typeKey.hashCode();
}
/**
* A base class for ProviderWithDependencies that need equality based on a specific object.
*/
private abstract static class RealOptionalBinderProviderWithDependencies<T> implements
ProviderWithDependencies<T> {
private final Object equality;
public RealOptionalBinderProviderWithDependencies(Object equality) {
this.equality = equality;
}
@Override
public boolean equals(Object obj) {
return this.getClass() == obj.getClass()
&& equality.equals(((RealOptionalBinderProviderWithDependencies<?>) obj).equality);
}
@Override
public int hashCode() {
return equality.hashCode();
}
}
@SuppressWarnings("rawtypes")
final class JavaOptionalProvider extends RealOptionalBinderProviderWithDependencies
implements ProviderWithExtensionVisitor, OptionalBinderBinding {
private JavaOptionalProvider() {
super(typeKey);
}
@Override
public Object get() {
Optional<Provider<T>> optional = optionalProviderT.get();
try {
if (optional.isPresent()) {
return JAVA_OF_NULLABLE_METHOD.invoke(JAVA_OPTIONAL_CLASS, optional.get().get());
} else {
return JAVA_EMPTY_METHOD.invoke(JAVA_OPTIONAL_CLASS);
}
} catch (IllegalAccessException e) {
throw new SecurityException(e);
} catch (IllegalArgumentException e) {
throw new IllegalStateException(e);
} catch (InvocationTargetException e) {
throw Throwables.propagate(e.getCause());
}
}
@Override
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
@SuppressWarnings("unchecked")
@Override
public Object acceptExtensionVisitor(BindingTargetVisitor visitor,
ProviderInstanceBinding binding) {
if (visitor instanceof MultibindingsTargetVisitor) {
return ((MultibindingsTargetVisitor) visitor).visit(this);
} else {
return visitor.visit(binding);
}
}
@Override
public boolean containsElement(Element element) {
return RealOptionalBinder.this.containsElement(element);
}
@Override
public Binding getActualBinding() {
return RealOptionalBinder.this.getActualBinding();
}
@Override
public Binding getDefaultBinding() {
return RealOptionalBinder.this.getDefaultBinding();
}
@Override
public Key getKey() {
return javaOptionalKey;
}
}
@SuppressWarnings("rawtypes")
final class JavaOptionalProviderProvider extends RealOptionalBinderProviderWithDependencies {
private JavaOptionalProviderProvider() {
super(typeKey);
}
@Override
public Object get() {
Optional<Provider<T>> optional = optionalProviderT.get();
try {
if (optional.isPresent()) {
return JAVA_OF_NULLABLE_METHOD.invoke(JAVA_OPTIONAL_CLASS, optional.get());
} else {
return JAVA_EMPTY_METHOD.invoke(JAVA_OPTIONAL_CLASS);
}
} catch (IllegalAccessException e) {
throw new SecurityException(e);
} catch (IllegalArgumentException e) {
throw new IllegalStateException(e);
} catch (InvocationTargetException e) {
throw Throwables.propagate(e.getCause());
}
}
@Override
public Set<Dependency<?>> getDependencies() {
return providerDependencies;
}
}
final class RealDirectTypeProvider extends RealOptionalBinderProviderWithDependencies<T> {
private RealDirectTypeProvider() {
super(typeKey);
}
@Override
public T get() {
Optional<Provider<T>> optional = optionalProviderT.get();
if (optional.isPresent()) {
return optional.get().get();
}
// Let Guice handle blowing up if the injection point doesn't have @Nullable
// (If it does have @Nullable, that's fine. This would only happen if
// setBinding/setDefault themselves were bound to 'null').
return null;
}
@Override
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
}
final class RealOptionalProviderProvider
extends RealOptionalBinderProviderWithDependencies<Optional<Provider<T>>> {
private Optional<Provider<T>> optional;
private RealOptionalProviderProvider() {
super(typeKey);
}
@Toolable
@Inject
void initialize(Injector injector) {
RealOptionalBinder.this.binder = null;
actualBinding = injector.getExistingBinding(actualKey);
defaultBinding = injector.getExistingBinding(defaultKey);
Binding<T> userBinding = injector.getExistingBinding(typeKey);
Binding<T> binding = null;
if (actualBinding != null) {
// TODO(sameb): Consider exposing an option that will allow
// ACTUAL to fallback to DEFAULT if ACTUAL's provider returns null.
// Right now, an ACTUAL binding can convert from present -> absent
// if it's bound to a provider that returns null.
binding = actualBinding;
} else if (defaultBinding != null) {
binding = defaultBinding;
} else if (userBinding != null) {
// If neither the actual or default is set, then we fallback
// to the value bound to the type itself and consider that the
// "actual binding" for the SPI.
binding = userBinding;
actualBinding = userBinding;
}
if (binding != null) {
optional = Optional.of(binding.getProvider());
RealOptionalBinder.this.dependencies =
ImmutableSet.<Dependency<?>>of(Dependency.get(binding.getKey()));
RealOptionalBinder.this.providerDependencies =
ImmutableSet.<Dependency<?>>of(Dependency.get(providerOf(binding.getKey())));
} else {
optional = Optional.absent();
RealOptionalBinder.this.dependencies = ImmutableSet.of();
RealOptionalBinder.this.providerDependencies = ImmutableSet.of();
}
}
@Override
public Optional<Provider<T>> get() {
return optional;
}
@Override
public Set<Dependency<?>> getDependencies() {
return providerDependencies;
}
}
final class RealOptionalKeyProvider
extends RealOptionalBinderProviderWithDependencies<Optional<T>>
implements ProviderWithExtensionVisitor<Optional<T>>,
OptionalBinderBinding<Optional<T>>,
Provider<Optional<T>> {
private RealOptionalKeyProvider() {
super(typeKey);
}
@Override
public Optional<T> get() {
Optional<Provider<T>> optional = optionalProviderT.get();
if (optional.isPresent()) {
return Optional.fromNullable(optional.get().get());
} else {
return Optional.absent();
}
}
@Override
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
@SuppressWarnings("unchecked")
@Override
public <B, R> R acceptExtensionVisitor(BindingTargetVisitor<B, R> visitor,
ProviderInstanceBinding<? extends B> binding) {
if (visitor instanceof MultibindingsTargetVisitor) {
return ((MultibindingsTargetVisitor<Optional<T>, R>) visitor).visit(this);
} else {
return visitor.visit(binding);
}
}
@Override
public Key<Optional<T>> getKey() {
return optionalKey;
}
@Override
public Binding<?> getActualBinding() {
return RealOptionalBinder.this.getActualBinding();
}
@Override
public Binding<?> getDefaultBinding() {
return RealOptionalBinder.this.getDefaultBinding();
}
@Override
public boolean containsElement(Element element) {
return RealOptionalBinder.this.containsElement(element);
}
}
}
static class DefaultImpl extends BaseAnnotation implements Default {
public DefaultImpl(String value) {
super(Default.class, value);
}
}
static class ActualImpl extends BaseAnnotation implements Actual {
public ActualImpl(String value) {
super(Actual.class, value);
}
}
abstract static class BaseAnnotation implements Serializable, Annotation {
private static final long serialVersionUID = 0;
private final String value;
private final Class<? extends Annotation> clazz;
BaseAnnotation(Class<? extends Annotation> clazz, String value) {
this.clazz = checkNotNull(clazz, "clazz");
this.value = checkNotNull(value, "value");
}
public String value() {
return this.value;
}
@Override
public int hashCode() {
// This is specified in java.lang.Annotation.
return (127 * "value".hashCode()) ^ value.hashCode();
}
@Override
public boolean equals(Object o) {
// We check against each annotation type instead of BaseAnnotation
// so that we can compare against generated annotation implementations.
if (o instanceof Actual && clazz == Actual.class) {
Actual other = (Actual) o;
return value.equals(other.value());
} else if (o instanceof Default && clazz == Default.class) {
Default other = (Default) o;
return value.equals(other.value());
}
return false;
}
@Override
public String toString() {
return "@" + clazz.getName() + (value.isEmpty() ? "" : "(value=" + value + ")");
}
@Override
public Class<? extends Annotation> annotationType() {
return clazz;
}
}
}

View file

@ -0,0 +1,58 @@
package com.google.inject.multibindings;
import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.spi.Element;
import com.google.inject.spi.Elements;
/**
* A binding for a OptionalBinder.
*
* <p>Although OptionalBinders may be injected through a variety of types
* {@code T}, {@code Optional<T>}, {@code Optional<Provider<T>>}, etc..), an
* OptionalBinderBinding exists only on the Binding associated with the
* {@code Optional<T>} key. Other bindings can be validated to be derived from this
* OptionalBinderBinding using {@link #containsElement}.
*
* @param <T> The fully qualified type of the optional binding, including Optional.
* For example: {@code Optional<String>}.
*/
public interface OptionalBinderBinding<T> {
/**
* Returns the {@link Key} for this binding.
*/
Key<T> getKey();
/**
* Returns the default binding (set by {@link OptionalBinder#setDefault}) if one exists or null
* if no default binding is set. This will throw {@link UnsupportedOperationException} if it is
* called on an element retrieved from {@link Elements#getElements}.
* <p>
* The Binding's type will always match the type Optional's generic type. For example, if getKey
* returns a key of <code>Optional&lt;String></code>, then this will always return a
* <code>Binding&lt;String></code>.
*/
Binding<?> getDefaultBinding();
/**
* Returns the actual binding (set by {@link OptionalBinder#setBinding}) or null if not set.
* This will throw {@link UnsupportedOperationException} if it is called on an element retrieved
* from {@link Elements#getElements}.
* <p>
* The Binding's type will always match the type Optional's generic type. For example, if getKey
* returns a key of <code>Optional&lt;String></code>, then this will always return a
* <code>Binding&lt;String></code>.
*/
Binding<?> getActualBinding();
/**
* Returns true if this OptionalBinder contains the given Element in order to build the optional
* binding or uses the given Element in order to support building and injecting its data. This
* will work for OptionalBinderBinding retrieved from an injector and
* {@link Elements#getElements}. Usually this is only necessary if you are working with elements
* retrieved from modules (without an Injector), otherwise {@link #getDefaultBinding} and
* {@link #getActualBinding} are better options.
*/
boolean containsElement(Element element);
}

View file

@ -0,0 +1,40 @@
package com.google.inject.multibindings;
import com.google.inject.Module;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotates methods of a {@link Module} to add items to a {@link MapBinder}.
* The method's return type, binding annotation and additional key annotation determines
* what Map this will contribute to. For example,
*
* <pre>
* {@literal @}ProvidesIntoMap
* {@literal @}StringMapKey("Foo")
* {@literal @}Named("plugins")
* Plugin provideFooUrl(FooManager fm) { returm fm.getPlugin(); }
*
* {@literal @}ProvidesIntoMap
* {@literal @}StringMapKey("Bar")
* {@literal @}Named("urls")
* Plugin provideBarUrl(BarManager bm) { return bm.getPlugin(); }
* </pre>
*
* will add two items to the {@code @Named("urls") Map<String, Plugin>} map. The key 'Foo'
* will map to the provideFooUrl method, and the key 'Bar' will map to the provideBarUrl method.
* The values are bound as providers and will be evaluated at injection time.
*
* <p>Because the key is specified as an annotation, only Strings, Classes, enums, primitive
* types and annotation instances are supported as keys.
*/
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface ProvidesIntoMap {
}

View file

@ -0,0 +1,50 @@
package com.google.inject.multibindings;
import com.google.inject.Module;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotates methods of a {@link Module} to add items to a {@link Multibinder}.
* The method's return type and binding annotation determines what Optional this will
* contribute to. For example,
*
* <pre>
* {@literal @}ProvidesIntoOptional(DEFAULT)
* {@literal @}Named("url")
* String provideFooUrl(FooManager fm) { returm fm.getUrl(); }
*
* {@literal @}ProvidesIntoOptional(ACTUAL)
* {@literal @}Named("url")
* String provideBarUrl(BarManager bm) { return bm.getUrl(); }
* </pre>
*
* will set the default value of {@code @Named("url") Optional<String>} to foo's URL,
* and then override it to bar's URL.
*/
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface ProvidesIntoOptional {
/**
* Specifies if the binding is for the actual or default value.
*/
Type value();
enum Type {
/**
* Corresponds to {@link OptionalBinder#setBinding}.
*/
ACTUAL,
/**
* Corresponds to {@link OptionalBinder#setDefault}.
*/
DEFAULT
}
}

View file

@ -0,0 +1,34 @@
package com.google.inject.multibindings;
import com.google.inject.Module;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotates methods of a {@link Module} to add items to a {@link Multibinder}.
* The method's return type and binding annotation determines what Set this will
* contribute to. For example,
*
* <pre>
* {@literal @}ProvidesIntoSet
* {@literal @}Named("urls")
* String provideFooUrl(FooManager fm) { returm fm.getUrl(); }
*
* {@literal @}ProvidesIntoSet
* {@literal @}Named("urls")
* String provideBarUrl(BarManager bm) { return bm.getUrl(); }
* </pre>
*
* will add two items to the {@code @Named("urls") Set<String>} set. The items are bound as
* providers and will be evaluated at injection time.
*/
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface ProvidesIntoSet {
}

View file

@ -0,0 +1,98 @@
package com.google.inject.multibindings;
import com.google.inject.Key;
import com.google.inject.internal.Annotations;
import java.lang.annotation.Annotation;
import java.util.concurrent.atomic.AtomicInteger;
/**
* An implementation of Element.
*/
// TODO(cgruber): Use AutoAnnotation when available, here & wherever else is makes sense.
class RealElement implements Element {
private static final AtomicInteger nextUniqueId = new AtomicInteger(1);
private final int uniqueId;
private final String setName;
private final Type type;
private final String keyType;
RealElement(String setName, Type type, String keyType) {
this(setName, type, keyType, nextUniqueId.incrementAndGet());
}
RealElement(String setName, Type type, String keyType, int uniqueId) {
this.uniqueId = uniqueId;
this.setName = setName;
this.type = type;
this.keyType = keyType;
}
/**
* Returns the name the binding should use. This is based on the annotation.
* If the annotation has an instance and is not a marker annotation,
* we ask the annotation for its toString. If it was a marker annotation
* or just an annotation type, we use the annotation's name. Otherwise,
* the name is the empty string.
*/
static String nameOf(Key<?> key) {
Annotation annotation = key.getAnnotation();
Class<? extends Annotation> annotationType = key.getAnnotationType();
if (annotation != null && !Annotations.isMarker(annotationType)) {
return key.getAnnotation().toString();
} else if (key.getAnnotationType() != null) {
return "@" + key.getAnnotationType().getName();
} else {
return "";
}
}
@Override
public String setName() {
return setName;
}
@Override
public int uniqueId() {
return uniqueId;
}
@Override
public Type type() {
return type;
}
@Override
public String keyType() {
return keyType;
}
@Override
public Class<? extends Annotation> annotationType() {
return Element.class;
}
@Override
public String toString() {
return "@" + Element.class.getName() + "(setName=" + setName
+ ",uniqueId=" + uniqueId + ", type=" + type + ", keyType=" + keyType + ")";
}
@Override
public boolean equals(Object o) {
return o instanceof Element
&& ((Element) o).setName().equals(setName())
&& ((Element) o).uniqueId() == uniqueId()
&& ((Element) o).type() == type()
&& ((Element) o).keyType().equals(keyType());
}
@Override
public int hashCode() {
return ((127 * "setName".hashCode()) ^ setName.hashCode())
+ ((127 * "uniqueId".hashCode()) ^ uniqueId)
+ ((127 * "type".hashCode()) ^ type.hashCode())
+ ((127 * "keyType".hashCode()) ^ keyType.hashCode());
}
}

View file

@ -0,0 +1,19 @@
package com.google.inject.multibindings;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Allows {@literal @}{@link ProvidesIntoMap} to specify a string map key.
*/
@MapKey(unwrapValue = true)
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface StringMapKey {
String value();
}

View file

@ -0,0 +1,202 @@
package com.google.inject.assistedinject;
import static com.google.inject.name.Names.named;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Stage;
import com.google.inject.name.Named;
import com.google.inject.spi.DefaultBindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.Element;
import com.google.inject.spi.Elements;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
/**
* Tests for AssistedInject Spi.
*
*/
public class ExtensionSpiTest extends TestCase {
public final void testSpiOnElements() throws Exception {
AssistedInjectSpiVisitor visitor = new AssistedInjectSpiVisitor();
Integer count = 0;
for(Element element : Elements.getElements(new Module())) {
if(element instanceof Binding) {
assertEquals(count++, ((Binding<?>)element).acceptTargetVisitor(visitor));
}
}
validateVisitor(visitor);
}
public void testSpiOnVisitor() throws Exception {
AssistedInjectSpiVisitor visitor = new AssistedInjectSpiVisitor();
Integer count = 0;
Injector injector = Guice.createInjector(new Module());
for(Binding<?> binding : injector.getBindings().values()) {
assertEquals(count++, binding.acceptTargetVisitor(visitor));
}
validateVisitor(visitor);
}
private void validateVisitor(AssistedInjectSpiVisitor visitor) throws Exception {
assertEquals(1, visitor.assistedBindingCount);
List<AssistedMethod> assistedMethods =
Lists.newArrayList(Iterables.getOnlyElement(
visitor.assistedInjectBindings).getAssistedMethods());
assertEquals(7, assistedMethods.size());
assertEquals(1, visitor.assistedBindingCount);
assertEquals(1, visitor.assistedInjectBindings.size());
// Validate for each of the methods in AnimalFactory
Set<String> names = Sets.newHashSet();
for (AssistedMethod method : assistedMethods) {
String name = method.getFactoryMethod().getName();
names.add(name);
switch (name) {
case "createAStrangeCatAsAnimal":
validateAssistedMethod(method, name, StrangeCat.class, ImmutableList.<Key<?>>of());
break;
case "createStrangeCatWithConstructorForOwner":
validateAssistedMethod(method, name, StrangeCat.class, ImmutableList.<Key<?>>of());
break;
case "createStrangeCatWithConstructorForAge":
validateAssistedMethod(method, name, StrangeCat.class, ImmutableList.<Key<?>>of());
break;
case "createCatWithANonAssistedDependency":
validateAssistedMethod(method, name, CatWithAName.class,
ImmutableList.<Key<?>>of(Key.get(String.class, named("catName2"))));
break;
case "createCat":
validateAssistedMethod(method, name, Cat.class, ImmutableList.<Key<?>>of());
break;
case "createASimpleCatAsAnimal":
validateAssistedMethod(method, name, SimpleCat.class, ImmutableList.<Key<?>>of());
break;
case "createCatWithNonAssistedDependencies":
List<Key<?>> dependencyKeys = ImmutableList.<Key<?>>of(
Key.get(String.class, named("catName1")),
Key.get(String.class, named("petName")),
Key.get(Integer.class, named("age")));
validateAssistedMethod(method, name, ExplodingCat.class, dependencyKeys);
break;
default:
fail("Invalid method: " + method);
break;
}
}
assertEquals(names, ImmutableSet.of("createAStrangeCatAsAnimal",
"createStrangeCatWithConstructorForOwner",
"createStrangeCatWithConstructorForAge",
"createCatWithANonAssistedDependency",
"createCat",
"createASimpleCatAsAnimal",
"createCatWithNonAssistedDependencies"));
}
private void validateAssistedMethod(AssistedMethod assistedMethod, String factoryMethodName,
Class clazz, List<Key<?>> dependencyKeys){
assertEquals(factoryMethodName, assistedMethod.getFactoryMethod().getName());
assertEquals(clazz, assistedMethod.getImplementationConstructor().getDeclaringClass());
assertEquals(dependencyKeys.size(), assistedMethod.getDependencies().size());
for (Dependency<?> dependency : assistedMethod.getDependencies()) {
assertTrue(dependencyKeys.contains(dependency.getKey()));
}
assertEquals(clazz, assistedMethod.getImplementationType().getType());
}
interface AnimalFactory {
Cat createCat(String owner);
CatWithAName createCatWithANonAssistedDependency(String owner);
@Named("SimpleCat") Animal createASimpleCatAsAnimal(String owner);
Animal createAStrangeCatAsAnimal(String owner);
StrangeCat createStrangeCatWithConstructorForOwner(String owner);
StrangeCat createStrangeCatWithConstructorForAge(Integer age);
ExplodingCat createCatWithNonAssistedDependencies(String owner);
}
interface Animal {}
private static class Cat implements Animal {
@Inject Cat(@Assisted String owner) {}
}
private static class SimpleCat implements Animal {
@Inject SimpleCat(@Assisted String owner) {
}
}
private static class StrangeCat implements Animal {
@AssistedInject StrangeCat(@Assisted String owner) {}
@AssistedInject StrangeCat(@Assisted Integer age) {}
}
private static class ExplodingCat implements Animal {
@Inject public ExplodingCat(@Named("catName1") String name, @Assisted String owner,
@Named("age") Integer age, @Named("petName") String petName) {}
}
private static class CatWithAName extends Cat {
@Inject CatWithAName(@Assisted String owner, @Named("catName2") String name) {
super(owner);
}
}
public class Module extends AbstractModule{
@Override
protected void configure() {
bind(String.class).annotatedWith(named("catName1")).toInstance("kitty1");
bind(String.class).annotatedWith(named("catName2")).toInstance("kitty2");
bind(String.class).annotatedWith(named("petName")).toInstance("pussy");
bind(Integer.class).annotatedWith(named("age")).toInstance(12);
install(new FactoryModuleBuilder()
.implement(Animal.class, StrangeCat.class)
.implement(Animal.class, named("SimpleCat"), SimpleCat.class)
.build(AnimalFactory.class));
}
}
public class AssistedInjectSpiVisitor extends DefaultBindingTargetVisitor<Object, Integer>
implements AssistedInjectTargetVisitor<Object, Integer> {
private final Set<Class> allowedClasses =
ImmutableSet.<Class> of(
Injector.class, Stage.class, Logger.class,
String.class, Integer.class);
private int assistedBindingCount = 0;
private int currentCount = 0;
private List<AssistedInjectBinding<?>> assistedInjectBindings = Lists.newArrayList();
public Integer visit(AssistedInjectBinding assistedInjectBinding) {
assistedInjectBindings.add(assistedInjectBinding);
assistedBindingCount++;
return currentCount++;
}
@Override
protected Integer visitOther(Binding<? extends Object> binding) {
if(!allowedClasses.contains(binding.getKey().getTypeLiteral().getRawType())) {
throw new AssertionFailedError("invalid other binding: " + binding);
}
return currentCount++;
}
}
}

View file

@ -0,0 +1,524 @@
package com.google.inject.assistedinject;
import static com.google.inject.Asserts.assertContains;
import static com.google.inject.name.Names.named;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.Element;
import com.google.inject.spi.Elements;
import com.google.inject.spi.HasDependencies;
import com.google.inject.spi.Message;
import junit.framework.TestCase;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class FactoryModuleBuilderTest extends TestCase {
private enum Color { BLUE, GREEN, RED, GRAY, BLACK }
public void testImplicitForwardingAssistedBindingFailsWithInterface() {
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Car.class).to(Golf.class);
install(new FactoryModuleBuilder().build(ColoredCarFactory.class));
}
});
fail();
} catch (CreationException ce) {
assertContains(
ce.getMessage(), "1) " + Car.class.getName() + " is an interface, not a concrete class.",
"Unable to create AssistedInject factory.",
"while locating " + Car.class.getName(),
"at " + ColoredCarFactory.class.getName() + ".create(");
assertEquals(1, ce.getErrorMessages().size());
}
}
public void testImplicitForwardingAssistedBindingFailsWithAbstractClass() {
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(AbstractCar.class).to(ArtCar.class);
install(new FactoryModuleBuilder().build(ColoredAbstractCarFactory.class));
}
});
fail();
} catch (CreationException ce) {
assertContains(
ce.getMessage(), "1) " + AbstractCar.class.getName() + " is abstract, not a concrete class.",
"Unable to create AssistedInject factory.",
"while locating " + AbstractCar.class.getName(),
"at " + ColoredAbstractCarFactory.class.getName() + ".create(");
assertEquals(1, ce.getErrorMessages().size());
}
}
public void testImplicitForwardingAssistedBindingCreatesNewObjects() {
final Mustang providedMustang = new Mustang(Color.BLUE);
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
install(new FactoryModuleBuilder().build(MustangFactory.class));
}
@Provides Mustang provide() { return providedMustang; }
});
assertSame(providedMustang, injector.getInstance(Mustang.class));
MustangFactory factory = injector.getInstance(MustangFactory.class);
Mustang created = factory.create(Color.GREEN);
assertNotSame(providedMustang, created);
assertEquals(Color.BLUE, providedMustang.color);
assertEquals(Color.GREEN, created.color);
}
public void testExplicitForwardingAssistedBindingFailsWithInterface() {
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Volkswagen.class).to(Golf.class);
install(new FactoryModuleBuilder()
.implement(Car.class, Volkswagen.class)
.build(ColoredCarFactory.class));
}
});
fail();
} catch (CreationException ce) {
assertContains(
ce.getMessage(), "1) " + Volkswagen.class.getName() + " is an interface, not a concrete class.",
"Unable to create AssistedInject factory.",
"while locating " + Volkswagen.class.getName(),
"while locating " + Car.class.getName(),
"at " + ColoredCarFactory.class.getName() + ".create(");
assertEquals(1, ce.getErrorMessages().size());
}
}
public void testExplicitForwardingAssistedBindingFailsWithAbstractClass() {
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(AbstractCar.class).to(ArtCar.class);
install(new FactoryModuleBuilder()
.implement(Car.class, AbstractCar.class)
.build(ColoredCarFactory.class));
}
});
fail();
} catch (CreationException ce) {
assertContains(
ce.getMessage(), "1) " + AbstractCar.class.getName() + " is abstract, not a concrete class.",
"Unable to create AssistedInject factory.",
"while locating " + AbstractCar.class.getName(),
"while locating " + Car.class.getName(),
"at " + ColoredCarFactory.class.getName() + ".create(");
assertEquals(1, ce.getErrorMessages().size());
}
}
public void testExplicitForwardingAssistedBindingCreatesNewObjects() {
final Mustang providedMustang = new Mustang(Color.BLUE);
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
install(new FactoryModuleBuilder().implement(Car.class, Mustang.class).build(
ColoredCarFactory.class));
}
@Provides Mustang provide() { return providedMustang; }
});
assertSame(providedMustang, injector.getInstance(Mustang.class));
ColoredCarFactory factory = injector.getInstance(ColoredCarFactory.class);
Mustang created = (Mustang)factory.create(Color.GREEN);
assertNotSame(providedMustang, created);
assertEquals(Color.BLUE, providedMustang.color);
assertEquals(Color.GREEN, created.color);
}
public void testAnnotatedAndParentBoundReturnValue() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Car.class).to(Golf.class);
bind(Integer.class).toInstance(911);
bind(Double.class).toInstance(5.0d);
install(new FactoryModuleBuilder()
.implement(Car.class, Names.named("german"), Beetle.class)
.implement(Car.class, Names.named("american"), Mustang.class)
.build(AnnotatedVersatileCarFactory.class));
}
});
AnnotatedVersatileCarFactory factory = injector.getInstance(AnnotatedVersatileCarFactory.class);
assertTrue(factory.getGermanCar(Color.BLACK) instanceof Beetle);
assertTrue(injector.getInstance(Car.class) instanceof Golf);
}
public void testParentBoundReturnValue() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Car.class).to(Golf.class);
bind(Double.class).toInstance(5.0d);
install(new FactoryModuleBuilder()
.implement(Car.class, Mustang.class)
.build(ColoredCarFactory.class));
}
});
ColoredCarFactory factory = injector.getInstance(ColoredCarFactory.class);
assertTrue(factory.create(Color.RED) instanceof Mustang);
assertTrue(injector.getInstance(Car.class) instanceof Golf);
}
public void testConfigureAnnotatedReturnValue() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
install(new FactoryModuleBuilder()
.implement(Car.class, Names.named("german"), Beetle.class)
.implement(Car.class, Names.named("american"), Mustang.class)
.build(AnnotatedVersatileCarFactory.class));
}
});
AnnotatedVersatileCarFactory factory = injector.getInstance(AnnotatedVersatileCarFactory.class);
assertTrue(factory.getGermanCar(Color.GRAY) instanceof Beetle);
assertTrue(factory.getAmericanCar(Color.BLACK) instanceof Mustang);
}
public void testNoBindingAssistedInject() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(MustangFactory.class));
}
});
MustangFactory factory = injector.getInstance(MustangFactory.class);
Mustang mustang = factory.create(Color.BLUE);
assertEquals(Color.BLUE, mustang.color);
}
public void testBindingAssistedInject() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder()
.implement(Car.class, Mustang.class)
.build(ColoredCarFactory.class));
}
});
ColoredCarFactory factory = injector.getInstance(ColoredCarFactory.class);
Mustang mustang = (Mustang) factory.create(Color.BLUE);
assertEquals(Color.BLUE, mustang.color);
}
public void testDuplicateBindings() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder()
.implement(Car.class, Mustang.class)
.build(ColoredCarFactory.class));
install(new FactoryModuleBuilder()
.implement(Car.class, Mustang.class)
.build(ColoredCarFactory.class));
}
});
ColoredCarFactory factory = injector.getInstance(ColoredCarFactory.class);
Mustang mustang = (Mustang) factory.create(Color.BLUE);
assertEquals(Color.BLUE, mustang.color);
}
public void testSimilarBindingsWithConflictingImplementations() {
try {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder()
.implement(Car.class, Mustang.class)
.build(ColoredCarFactory.class));
install(new FactoryModuleBuilder()
.implement(Car.class, Golf.class)
.build(ColoredCarFactory.class));
}
});
injector.getInstance(ColoredCarFactory.class);
fail();
} catch (CreationException ce) {
assertContains(ce.getMessage(),
"A binding to " + ColoredCarFactory.class.getName() + " was already configured");
assertEquals(1, ce.getErrorMessages().size());
}
}
public void testMultipleReturnTypes() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Double.class).toInstance(5.0d);
install(new FactoryModuleBuilder().build(VersatileCarFactory.class));
}
});
VersatileCarFactory factory = injector.getInstance(VersatileCarFactory.class);
Mustang mustang = factory.getMustang(Color.RED);
assertEquals(Color.RED, mustang.color);
Beetle beetle = factory.getBeetle(Color.GREEN);
assertEquals(Color.GREEN, beetle.color);
}
public void testParameterizedClassesWithNoImplements() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(new TypeLiteral<Foo.Factory<String>>() {}));
}
});
Foo.Factory<String> factory = injector.getInstance(Key.get(new TypeLiteral<Foo.Factory<String>>() {}));
@SuppressWarnings("unused")
Foo<String> foo = factory.create(new Bar());
}
public void testGenericErrorMessageMakesSense() {
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(Key.get(Foo.Factory.class)));
}
});
fail();
} catch(CreationException ce ) {
// Assert not only that it's the correct message, but also that it's the *only* message.
Collection<Message> messages = ce.getErrorMessages();
assertEquals(
Foo.Factory.class.getName() + " cannot be used as a key; It is not fully specified.",
Iterables.getOnlyElement(messages).getMessage());
}
}
interface Car {}
interface Volkswagen extends Car {}
interface ColoredCarFactory {
Car create(Color color);
}
interface MustangFactory {
Mustang create(Color color);
}
interface VersatileCarFactory {
Mustang getMustang(Color color);
Beetle getBeetle(Color color);
}
interface AnnotatedVersatileCarFactory {
@Named("german") Car getGermanCar(Color color);
@Named("american") Car getAmericanCar(Color color);
}
public static class Golf implements Volkswagen {}
public static class Mustang implements Car {
private final Color color;
@Inject
public Mustang(@Assisted Color color) {
this.color = color;
}
}
public static class Beetle implements Car {
private final Color color;
@Inject
public Beetle(@Assisted Color color) {
this.color = color;
}
}
public static class Foo<E> {
static interface Factory<E> {
Foo<E> create(Bar bar);
}
@SuppressWarnings("unused")
@Inject Foo(@Assisted Bar bar, Baz<E> baz) {}
}
public static class Bar {}
@SuppressWarnings("unused")
public static class Baz<E> {}
abstract static class AbstractCar implements Car {}
interface ColoredAbstractCarFactory {
AbstractCar create(Color color);
}
public static class ArtCar extends AbstractCar {}
public void testFactoryBindingDependencies() {
// validate dependencies work in all stages & as a raw element,
// and that dependencies work for methods, fields, constructors,
// and for @AssistedInject constructors too.
Module module = new AbstractModule() {
@Override
protected void configure() {
bind(Integer.class).toInstance(42);
bind(Double.class).toInstance(4.2d);
bind(Float.class).toInstance(4.2f);
bind(String.class).annotatedWith(named("dog")).toInstance("dog");
bind(String.class).annotatedWith(named("cat1")).toInstance("cat1");
bind(String.class).annotatedWith(named("cat2")).toInstance("cat2");
bind(String.class).annotatedWith(named("cat3")).toInstance("cat3");
bind(String.class).annotatedWith(named("arbitrary")).toInstance("fail!");
install(new FactoryModuleBuilder()
.implement(Animal.class, Dog.class)
.build(AnimalHouse.class));
}
};
Set<Key<?>> expectedKeys = ImmutableSet.<Key<?>>of(
Key.get(Integer.class),
Key.get(Double.class),
Key.get(Float.class),
Key.get(String.class, named("dog")),
Key.get(String.class, named("cat1")),
Key.get(String.class, named("cat2")),
Key.get(String.class, named("cat3"))
);
Injector injector = Guice.createInjector(module);
validateDependencies(expectedKeys, injector.getBinding(AnimalHouse.class));
injector = Guice.createInjector(Stage.TOOL, module);
validateDependencies(expectedKeys, injector.getBinding(AnimalHouse.class));
List<Element> elements = Elements.getElements(module);
boolean found = false;
for(Element element : elements) {
if(element instanceof Binding) {
Binding<?> binding = (Binding<?>) element;
if(binding.getKey().equals(Key.get(AnimalHouse.class))) {
found = true;
validateDependencies(expectedKeys, binding);
break;
}
}
}
assertTrue(found);
}
private void validateDependencies(Set<Key<?>> expectedKeys, Binding<?> binding) {
Set<Dependency<?>> dependencies = ((HasDependencies)binding).getDependencies();
Set<Key<?>> actualKeys = new HashSet<Key<?>>();
for (Dependency<?> dependency : dependencies) {
actualKeys.add(dependency.getKey());
}
assertEquals(expectedKeys, actualKeys);
}
interface AnimalHouse {
Animal createAnimal(String name);
Cat createCat(String name);
Cat createCat(int age);
}
interface Animal {}
@SuppressWarnings("unused")
private static class Dog implements Animal {
@Inject int a;
@Inject Dog(@Assisted String a, double b) {}
@Inject void register(@Named("dog") String a) {}
}
@SuppressWarnings("unused")
private static class Cat implements Animal {
@Inject float a;
@AssistedInject Cat(@Assisted String a, @Named("cat1") String b) {}
@AssistedInject Cat(@Assisted int a, @Named("cat2") String b) {}
@AssistedInject Cat(@Assisted byte a, @Named("catfail") String b) {} // not a dependency!
@Inject void register(@Named("cat3") String a) {}
}
public void testFactoryPublicAndReturnTypeNotPublic() {
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder()
.implement(Hidden.class, HiddenImpl.class)
.build(NotHidden.class));
}
});
} catch(CreationException ce) {
assertEquals(NotHidden.class.getName() + " is public, but has a method that returns a non-public type: "
+ Hidden.class.getName() + ". Due to limitations with java.lang.reflect.Proxy, this is not allowed. "
+ "Please either make the factory non-public or the return type public.",
Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
}
}
interface Hidden {}
public static class HiddenImpl implements Hidden {}
public interface NotHidden {
Hidden create();
}
public void testSingletonScopeOnAssistedClassIsIgnored() {
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(SingletonFactory.class));
}
});
fail();
} catch (CreationException ce) {
assertEquals(1, ce.getErrorMessages().size());
assertEquals("Found scope annotation [" + Singleton.class.getName() + "]"
+ " on implementation class [" + AssistedSingleton.class.getName() + "]"
+ " of AssistedInject factory [" + SingletonFactory.class.getName() + "]."
+ "\nThis is not allowed, please remove the scope annotation.",
Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
}
}
interface SingletonFactory {
AssistedSingleton create(String string);
}
@SuppressWarnings("GuiceAssistedInjectScoping")
@Singleton
static class AssistedSingleton {
@Inject
public AssistedSingleton(@SuppressWarnings("unused") @Assisted String string) {
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,840 @@
/**
* 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.Asserts.assertContains;
import com.google.common.collect.ImmutableSet;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.ConfigurationException;
import com.google.inject.CreationException;
import com.google.inject.Guice;
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.assistedinject.FactoryProviderTest.Equals.ComparisonMethod;
import com.google.inject.assistedinject.FactoryProviderTest.Equals.Impl;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies;
import junit.framework.TestCase;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
/**
* @author jmourits@google.com (Jerome Mourits)
* @author jessewilson@google.com (Jesse Wilson)
*/
@SuppressWarnings("deprecation")
public class FactoryProviderTest extends TestCase {
private enum Color { BLUE, GREEN, RED, GRAY, BLACK, ORANGE, PINK }
public void testAssistedFactory() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Double.class).toInstance(5.0d);
bind(ColoredCarFactory.class)
.toProvider(FactoryProvider.newFactory(ColoredCarFactory.class, Mustang.class));
}
});
ColoredCarFactory carFactory = injector.getInstance(ColoredCarFactory.class);
Mustang blueMustang = (Mustang) carFactory.create(Color.BLUE);
assertEquals(Color.BLUE, blueMustang.color);
assertEquals(5.0d, blueMustang.engineSize);
Mustang redMustang = (Mustang) carFactory.create(Color.RED);
assertEquals(Color.RED, redMustang.color);
assertEquals(5.0d, redMustang.engineSize);
}
public void testFactoryBindingDependencies() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Double.class).toInstance(5.0d);
bind(ColoredCarFactory.class)
.toProvider(FactoryProvider.newFactory(ColoredCarFactory.class, Mustang.class));
}
});
Binding<?> binding = injector.getBinding(ColoredCarFactory.class);
HasDependencies hasDependencies = (HasDependencies) binding;
assertEquals(ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(double.class))),
hasDependencies.getDependencies());
}
public void testAssistedFactoryWithAnnotations() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(int.class).annotatedWith(Names.named("horsePower")).toInstance(250);
bind(int.class).annotatedWith(Names.named("modelYear")).toInstance(1984);
bind(ColoredCarFactory.class)
.toProvider(FactoryProvider.newFactory(ColoredCarFactory.class, Camaro.class));
}
});
ColoredCarFactory carFactory = injector.getInstance(ColoredCarFactory.class);
Camaro blueCamaro = (Camaro) carFactory.create(Color.BLUE);
assertEquals(Color.BLUE, blueCamaro.color);
assertEquals(1984, blueCamaro.modelYear);
assertEquals(250, blueCamaro.horsePower);
Camaro redCamaro = (Camaro) carFactory.create(Color.RED);
assertEquals(Color.RED, redCamaro.color);
assertEquals(1984, redCamaro.modelYear);
assertEquals(250, redCamaro.horsePower);
}
interface Car {
}
interface ColoredCarFactory {
Car create(Color color);
}
public static class Mustang implements Car {
private final double engineSize;
private final Color color;
@AssistedInject
public Mustang(double engineSize, @Assisted Color color) {
this.engineSize = engineSize;
this.color = color;
}
}
public static class Camaro implements Car {
private final int horsePower;
private final int modelYear;
private final Color color;
@AssistedInject
public Camaro(
@Named("horsePower")int horsePower,
@Named("modelYear")int modelYear,
@Assisted Color color) {
this.horsePower = horsePower;
this.modelYear = modelYear;
this.color = color;
}
}
interface SummerCarFactory {
Car create(Color color, boolean convertable);
Car createConvertible(Color color);
}
public void testFactoryWithMultipleMethods() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(float.class).toInstance(140f);
bind(SummerCarFactory.class).toProvider(
FactoryProvider.newFactory(SummerCarFactory.class, Corvette.class));
}
});
SummerCarFactory carFactory = injector.getInstance(SummerCarFactory.class);
Corvette blueCorvette = (Corvette) carFactory.createConvertible(Color.BLUE);
assertEquals(Color.BLUE, blueCorvette.color);
assertEquals(100f, blueCorvette.maxMph);
assertTrue(blueCorvette.isConvertable);
Corvette redCorvette = (Corvette) carFactory.create(Color.RED, false);
assertEquals(Color.RED, redCorvette.color);
assertEquals(140f, redCorvette.maxMph);
assertFalse(redCorvette.isConvertable);
}
public static class Corvette implements Car {
private boolean isConvertable;
private Color color;
private float maxMph;
@AssistedInject
public Corvette(@Assisted Color color) {
this(color, 100f, true);
}
@SuppressWarnings("unused")
public Corvette(@Assisted Color color, @Assisted boolean isConvertable) {
throw new IllegalStateException("Not an @AssistedInject constructor");
}
@AssistedInject
public Corvette(@Assisted Color color, Float maxMph, @Assisted boolean isConvertable) {
this.isConvertable = isConvertable;
this.color = color;
this.maxMph = maxMph;
}
}
public void testFactoryMethodsMismatch() {
try {
FactoryProvider.newFactory(SummerCarFactory.class, Beetle.class);
fail();
} catch(ConfigurationException e) {
assertContains(e.getMessage(), "Constructor mismatch");
}
}
public static class Beetle implements Car {
@AssistedInject
@SuppressWarnings("unused")
public Beetle(@Assisted Color color) {
throw new IllegalStateException("Conflicting constructors");
}
@AssistedInject
@SuppressWarnings("unused")
public Beetle(@Assisted Color color, @Assisted boolean isConvertable) {
throw new IllegalStateException("Conflicting constructors");
}
@AssistedInject
@SuppressWarnings("unused")
public Beetle(@Assisted Color color, @Assisted boolean isConvertable, float maxMph) {
throw new IllegalStateException("Conflicting constructors");
}
}
public void testMethodsAndFieldsGetInjected() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(String.class).toInstance("turbo");
bind(int.class).toInstance(911);
bind(double.class).toInstance(50000d);
bind(ColoredCarFactory.class)
.toProvider(FactoryProvider.newFactory(ColoredCarFactory.class, Porshe.class));
}
});
ColoredCarFactory carFactory = injector.getInstance(ColoredCarFactory.class);
Porshe grayPorshe = (Porshe) carFactory.create(Color.GRAY);
assertEquals(Color.GRAY, grayPorshe.color);
assertEquals(50000d, grayPorshe.price);
assertEquals(911, grayPorshe.model);
assertEquals("turbo", grayPorshe.name);
}
public static class Porshe implements Car {
private final Color color;
private final double price;
private @Inject String name;
private int model;
@AssistedInject
public Porshe(@Assisted Color color, double price) {
this.color = color;
this.price = price;
}
@Inject void setModel(int model) {
this.model = model;
}
}
public void testProviderInjection() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(String.class).toInstance("trans am");
bind(ColoredCarFactory.class)
.toProvider(FactoryProvider.newFactory(ColoredCarFactory.class, Firebird.class));
}
});
ColoredCarFactory carFactory = injector.getInstance(ColoredCarFactory.class);
Firebird blackFirebird = (Firebird) carFactory.create(Color.BLACK);
assertEquals(Color.BLACK, blackFirebird.color);
assertEquals("trans am", blackFirebird.modifiersProvider.get());
}
public static class Firebird implements Car {
private final Provider<String> modifiersProvider;
private final Color color;
@AssistedInject
public Firebird(Provider<String> modifiersProvider, @Assisted Color color) {
this.modifiersProvider = modifiersProvider;
this.color = color;
}
}
public void testTypeTokenInjection() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<Set<String>>() {}).toInstance(Collections.singleton("Flux Capacitor"));
bind(new TypeLiteral<Set<Integer>>() {}).toInstance(Collections.singleton(88));
bind(ColoredCarFactory.class)
.toProvider(FactoryProvider.newFactory(ColoredCarFactory.class, DeLorean.class));
}
});
ColoredCarFactory carFactory = injector.getInstance(ColoredCarFactory.class);
DeLorean deLorean = (DeLorean) carFactory.create(Color.GRAY);
assertEquals(Color.GRAY, deLorean.color);
assertEquals("Flux Capacitor", deLorean.features.iterator().next());
assertEquals(new Integer(88), deLorean.featureActivationSpeeds.iterator().next());
}
public static class DeLorean implements Car {
private final Set<String> features;
private final Set<Integer> featureActivationSpeeds;
private final Color color;
@AssistedInject
public DeLorean(
Set<String> extraFeatures, Set<Integer> featureActivationSpeeds, @Assisted Color color) {
this.features = extraFeatures;
this.featureActivationSpeeds = featureActivationSpeeds;
this.color = color;
}
}
public void testTypeTokenProviderInjection() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<Set<String>>() { }).toInstance(Collections.singleton("Datsun"));
bind(ColoredCarFactory.class)
.toProvider(FactoryProvider.newFactory(ColoredCarFactory.class, Z.class));
}
});
ColoredCarFactory carFactory = injector.getInstance(ColoredCarFactory.class);
Z orangeZ = (Z) carFactory.create(Color.ORANGE);
assertEquals(Color.ORANGE, orangeZ.color);
assertEquals("Datsun", orangeZ.manufacturersProvider.get().iterator().next());
}
public static class Z implements Car {
private final Provider<Set<String>> manufacturersProvider;
private final Color color;
@AssistedInject
public Z(Provider<Set<String>> manufacturersProvider, @Assisted Color color) {
this.manufacturersProvider = manufacturersProvider;
this.color = color;
}
}
public static class Prius implements Car {
@SuppressWarnings("unused")
private final Color color;
@AssistedInject
private Prius(@Assisted Color color) {
this.color = color;
}
}
public void testAssistInjectionInNonPublicConstructor() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(ColoredCarFactory.class)
.toProvider(FactoryProvider.newFactory(ColoredCarFactory.class, Prius.class));
}
});
injector.getInstance(ColoredCarFactory.class).create(Color.ORANGE);
}
public static class ExplodingCar implements Car {
@AssistedInject
public ExplodingCar(@SuppressWarnings("unused") @Assisted Color color) {
throw new IllegalStateException("kaboom!");
}
}
public void testExceptionDuringConstruction() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(ColoredCarFactory.class).toProvider(
FactoryProvider.newFactory(ColoredCarFactory.class, ExplodingCar.class));
}
});
try {
injector.getInstance(ColoredCarFactory.class).create(Color.ORANGE);
fail();
} catch (IllegalStateException e) {
assertEquals("kaboom!", e.getMessage());
}
}
public static class DefectiveCar implements Car {
@AssistedInject
public DefectiveCar() throws ExplosionException {
throw new ExplosionException();
}
}
public static class ExplosionException extends Exception { }
public static class FireException extends Exception { }
public interface DefectiveCarFactoryWithNoExceptions {
Car createCar();
}
public interface DefectiveCarFactory {
Car createCar() throws FireException;
}
public void testFactoryMethodMustDeclareAllConstructorExceptions() {
try {
FactoryProvider.newFactory(DefectiveCarFactoryWithNoExceptions.class, DefectiveCar.class);
fail();
} catch (ConfigurationException expected) {
assertContains(expected.getMessage(), "no compatible exception is thrown");
}
}
public interface CorrectDefectiveCarFactory {
Car createCar() throws FireException, ExplosionException;
}
public void testConstructorExceptionsAreThrownByFactory() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(CorrectDefectiveCarFactory.class).toProvider(
FactoryProvider.newFactory(
CorrectDefectiveCarFactory.class, DefectiveCar.class));
}
});
try {
injector.getInstance(CorrectDefectiveCarFactory.class).createCar();
fail();
} catch (FireException e) {
fail();
} catch (ExplosionException expected) {
}
}
public static class MultipleConstructorDefectiveCar implements Car {
@AssistedInject
public MultipleConstructorDefectiveCar() throws ExplosionException {
throw new ExplosionException();
}
@AssistedInject
public MultipleConstructorDefectiveCar(@SuppressWarnings("unused") @Assisted Color c)
throws FireException {
throw new FireException();
}
}
public interface MultipleConstructorDefectiveCarFactory {
Car createCar() throws ExplosionException;
Car createCar(Color r) throws FireException;
}
public void testMultipleConstructorExceptionMatching() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(MultipleConstructorDefectiveCarFactory.class).toProvider(
FactoryProvider.newFactory(
MultipleConstructorDefectiveCarFactory.class,
MultipleConstructorDefectiveCar.class));
}
});
MultipleConstructorDefectiveCarFactory factory
= injector.getInstance(MultipleConstructorDefectiveCarFactory.class);
try {
factory.createCar();
fail();
} catch (ExplosionException expected) {
}
try {
factory.createCar(Color.RED);
fail();
} catch (FireException expected) {
}
}
public static class WildcardCollection {
public interface Factory {
WildcardCollection create(Collection<?> items);
}
@AssistedInject
public WildcardCollection(@SuppressWarnings("unused") @Assisted Collection<?> items) { }
}
public void testWildcardGenerics() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(WildcardCollection.Factory.class).toProvider(
FactoryProvider.newFactory(
WildcardCollection.Factory.class,
WildcardCollection.class));
}
});
WildcardCollection.Factory factory = injector.getInstance(WildcardCollection.Factory.class);
factory.create(Collections.emptyList());
}
public static class SteeringWheel {}
public static class Fiat implements Car {
@SuppressWarnings("unused")
private final SteeringWheel steeringWheel;
@SuppressWarnings("unused")
private final Color color;
@AssistedInject
public Fiat(SteeringWheel steeringWheel, @Assisted Color color) {
this.steeringWheel = steeringWheel;
this.color = color;
}
}
public void testFactoryWithImplicitBindings() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(ColoredCarFactory.class).toProvider(
FactoryProvider.newFactory(
ColoredCarFactory.class,
Fiat.class));
}
});
ColoredCarFactory coloredCarFactory = injector.getInstance(ColoredCarFactory.class);
Fiat fiat = (Fiat) coloredCarFactory.create(Color.GREEN);
assertEquals(Color.GREEN, fiat.color);
assertNotNull(fiat.steeringWheel);
}
public void testFactoryFailsWithMissingBinding() {
try {
Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(ColoredCarFactory.class)
.toProvider(FactoryProvider.newFactory(ColoredCarFactory.class, Mustang.class));
}
});
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(),
"1) Parameter of type 'double' is not injectable or annotated with @Assisted");
}
}
public void testMethodsDeclaredInObject() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Double.class).toInstance(5.0d);
bind(ColoredCarFactory.class)
.toProvider(FactoryProvider.newFactory(ColoredCarFactory.class, Mustang.class));
}
});
ColoredCarFactory carFactory = injector.getInstance(ColoredCarFactory.class);
carFactory.equals(carFactory);
carFactory.hashCode();
carFactory.toString();
}
public void testAssistedInjectConstructorAndAssistedFactoryParameterMustNotMix() {
try {
Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Double.class).toInstance(5.0d);
bind(AssistedParamsFactory.class)
.toProvider(FactoryProvider.newFactory(AssistedParamsFactory.class, Mustang.class));
}
});
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(), "Factory method "
+ AssistedParamsFactory.class.getName() + ".create() has an @Assisted parameter, which "
+ "is incompatible with the deprecated @AssistedInject annotation.");
}
}
interface AssistedParamsFactory {
Car create(@Assisted Color color);
}
interface GenericColoredCarFactory<T extends Car> {
T create(Color color);
}
public void testGenericAssistedFactory() {
final TypeLiteral<GenericColoredCarFactory<Mustang>> mustangTypeLiteral
= new TypeLiteral<GenericColoredCarFactory<Mustang>>() {};
final TypeLiteral<GenericColoredCarFactory<Camaro>> camaroTypeLiteral
= new TypeLiteral<GenericColoredCarFactory<Camaro>>() {};
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Double.class).toInstance(5.0d);
bind(int.class).annotatedWith(Names.named("horsePower")).toInstance(250);
bind(int.class).annotatedWith(Names.named("modelYear")).toInstance(1984);
bind(mustangTypeLiteral).toProvider(
FactoryProvider.newFactory(mustangTypeLiteral, TypeLiteral.get(Mustang.class)));
bind(camaroTypeLiteral).toProvider(
FactoryProvider.newFactory(camaroTypeLiteral, TypeLiteral.get(Camaro.class)));
}
});
GenericColoredCarFactory<Mustang> mustangFactory
= injector.getInstance(Key.get(mustangTypeLiteral));
GenericColoredCarFactory<Camaro> camaroFactory
= injector.getInstance(Key.get(camaroTypeLiteral));
Mustang blueMustang = mustangFactory.create(Color.BLUE);
assertEquals(Color.BLUE, blueMustang.color);
assertEquals(5.0d, blueMustang.engineSize);
Camaro redCamaro = camaroFactory.create(Color.RED);
assertEquals(Color.RED, redCamaro.color);
assertEquals(1984, redCamaro.modelYear);
assertEquals(250, redCamaro.horsePower);
}
@SuppressWarnings("unused")
public interface Insurance<T extends Car> {
}
public static class MustangInsurance implements Insurance<Mustang> {
private final double premium;
private final double limit;
@SuppressWarnings("unused") private Mustang car;
@AssistedInject
public MustangInsurance(@Named("lowLimit") double limit, @Assisted Mustang car,
@Assisted double premium) {
this.premium = premium;
this.limit = limit;
this.car = car;
}
public void sell() {}
}
public static class CamaroInsurance implements Insurance<Camaro> {
private final double premium;
private final double limit;
@SuppressWarnings("unused") private Camaro car;
@AssistedInject
public CamaroInsurance(@Named("highLimit") double limit, @Assisted Camaro car,
@Assisted double premium) {
this.premium = premium;
this.limit = limit;
this.car = car;
}
public void sell() {}
}
public interface MustangInsuranceFactory {
public Insurance<Mustang> create(Mustang car, double premium);
}
public interface CamaroInsuranceFactory {
public Insurance<Camaro> create(Camaro car, double premium);
}
public void testAssistedFactoryForConcreteType() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Double.class).annotatedWith(Names.named("lowLimit")).toInstance(50000.0d);
bind(Double.class).annotatedWith(Names.named("highLimit")).toInstance(100000.0d);
bind(MustangInsuranceFactory.class).toProvider(
FactoryProvider.newFactory(MustangInsuranceFactory.class, MustangInsurance.class));
bind(CamaroInsuranceFactory.class).toProvider(
FactoryProvider.newFactory(CamaroInsuranceFactory.class, CamaroInsurance.class));
}
});
MustangInsuranceFactory mustangInsuranceFactory =
injector.getInstance(MustangInsuranceFactory.class);
CamaroInsuranceFactory camaroInsuranceFactory =
injector.getInstance(CamaroInsuranceFactory.class);
Mustang mustang = new Mustang(5000d, Color.BLACK);
MustangInsurance mustangPolicy =
(MustangInsurance) mustangInsuranceFactory.create(mustang, 800.0d);
assertEquals(800.0d, mustangPolicy.premium);
assertEquals(50000.0d, mustangPolicy.limit);
Camaro camaro = new Camaro(3000, 1967, Color.BLUE);
CamaroInsurance camaroPolicy = (CamaroInsurance) camaroInsuranceFactory.create(camaro, 800.0d);
assertEquals(800.0d, camaroPolicy.premium);
assertEquals(100000.0d, camaroPolicy.limit);
}
public interface InsuranceFactory<T extends Car> {
public Insurance<T> create(T car, double premium);
}
public void testAssistedFactoryForParameterizedType() {
final TypeLiteral<InsuranceFactory<Mustang>> mustangInsuranceFactoryType =
new TypeLiteral<InsuranceFactory<Mustang>>() {};
final TypeLiteral<InsuranceFactory<Camaro>> camaroInsuranceFactoryType =
new TypeLiteral<InsuranceFactory<Camaro>>() {};
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Double.class).annotatedWith(Names.named("lowLimit")).toInstance(50000.0d);
bind(Double.class).annotatedWith(Names.named("highLimit")).toInstance(100000.0d);
bind(mustangInsuranceFactoryType).toProvider(FactoryProvider.newFactory(
mustangInsuranceFactoryType, TypeLiteral.get(MustangInsurance.class)));
bind(camaroInsuranceFactoryType).toProvider(FactoryProvider.newFactory(
camaroInsuranceFactoryType, TypeLiteral.get(CamaroInsurance.class)));
}
});
InsuranceFactory<Mustang> mustangInsuranceFactory =
injector.getInstance(Key.get(mustangInsuranceFactoryType));
InsuranceFactory<Camaro> camaroInsuranceFactory =
injector.getInstance(Key.get(camaroInsuranceFactoryType));
Mustang mustang = new Mustang(5000d, Color.BLACK);
MustangInsurance mustangPolicy =
(MustangInsurance) mustangInsuranceFactory.create(mustang, 800.0d);
assertEquals(800.0d, mustangPolicy.premium);
assertEquals(50000.0d, mustangPolicy.limit);
Camaro camaro = new Camaro(3000, 1967, Color.BLUE);
CamaroInsurance camaroPolicy = (CamaroInsurance) camaroInsuranceFactory.create(camaro, 800.0d);
assertEquals(800.0d, camaroPolicy.premium);
assertEquals(100000.0d, camaroPolicy.limit);
}
public static class AutoInsurance<T extends Car> implements Insurance<T> {
private final double premium;
private final double limit;
private final T car;
@AssistedInject
public AutoInsurance(double limit, @Assisted T car, @Assisted double premium) {
this.limit = limit;
this.car = car;
this.premium = premium;
}
public void sell() {}
}
public void testAssistedFactoryForTypeVariableParameters() {
final TypeLiteral<InsuranceFactory<Camaro>> camaroInsuranceFactoryType =
new TypeLiteral<InsuranceFactory<Camaro>>() {};
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Double.class).toInstance(50000.0d);
bind(camaroInsuranceFactoryType).toProvider(FactoryProvider.newFactory(
camaroInsuranceFactoryType, new TypeLiteral<AutoInsurance<Camaro>>() {}));
}
});
InsuranceFactory<Camaro> camaroInsuranceFactory =
injector.getInstance(Key.get(camaroInsuranceFactoryType));
Camaro camaro = new Camaro(3000, 1967, Color.BLUE);
AutoInsurance<?> camaroPolicy =
(AutoInsurance<?>) camaroInsuranceFactory.create(camaro, 800.0d);
assertEquals(800.0d, camaroPolicy.premium);
assertEquals(50000.0d, camaroPolicy.limit);
assertEquals(camaro, camaroPolicy.car);
}
public void testDuplicateAssistedFactoryBinding() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Double.class).toInstance(5.0d);
bind(ColoredCarFactory.class)
.toProvider(FactoryProvider.newFactory(ColoredCarFactory.class, Mustang.class));
bind(ColoredCarFactory.class)
.toProvider(FactoryProvider.newFactory(ColoredCarFactory.class, Mustang.class));
}
});
ColoredCarFactory carFactory = injector.getInstance(ColoredCarFactory.class);
Mustang blueMustang = (Mustang) carFactory.create(Color.BLUE);
assertEquals(Color.BLUE, blueMustang.color);
assertEquals(5.0d, blueMustang.engineSize);
Mustang redMustang = (Mustang) carFactory.create(Color.RED);
assertEquals(Color.RED, redMustang.color);
assertEquals(5.0d, redMustang.engineSize);
}
public interface Equals {
enum ComparisonMethod { SHALLOW, DEEP; }
interface Factory {
Equals equals(ComparisonMethod comparisonMethod);
}
public static class Impl implements Equals {
private final double sigma;
private final ComparisonMethod comparisonMethod;
@Inject
public Impl(double sigma, @Assisted ComparisonMethod comparisonMethod) {
this.sigma = sigma;
this.comparisonMethod = comparisonMethod;
}
}
}
public void testFactoryMethodCalledEquals() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Double.class).toInstance(0.01d);
bind(Equals.Factory.class).toProvider(
FactoryProvider.newFactory(Equals.Factory.class, Impl.class));
}
});
Equals.Factory equalsFactory = injector.getInstance(Equals.Factory.class);
Impl shallowEquals = (Impl) equalsFactory.equals(ComparisonMethod.SHALLOW);
assertEquals(ComparisonMethod.SHALLOW, shallowEquals.comparisonMethod);
assertEquals(0.01d, shallowEquals.sigma);
}
}

View file

@ -0,0 +1,258 @@
package com.google.inject.assistedinject;
import com.google.inject.AbstractModule;
import com.google.inject.Asserts;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import junit.framework.TestCase;
public class ManyConstructorsTest extends TestCase {
public void testTwoConstructors() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(Factory.class));
}
});
Factory factory = injector.getInstance(Factory.class);
Foo noIndex = factory.create("no index");
assertEquals("no index", noIndex.name);
assertNull(noIndex.index);
Foo index = factory.create("index", 1);
assertEquals("index", index.name);
assertEquals(1, index.index.intValue());
}
public void testDifferentOrderParameters() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(OtherFactory.class));
}
});
OtherFactory factory = injector.getInstance(OtherFactory.class);
Foo noIndex = factory.create("no index");
assertEquals("no index", noIndex.name);
assertNull(noIndex.index);
Foo index = factory.create(1, "index");
assertEquals("index", index.name);
assertEquals(1, index.index.intValue());
Foo index2 = factory.create("index", 2);
assertEquals("index", index2.name);
assertEquals(2, index2.index.intValue());
}
public void testInterfaceToImpl() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder()
.implement(Bar.class, Foo.class)
.build(BarFactory.class));
}
});
BarFactory factory = injector.getInstance(BarFactory.class);
Bar noIndex = factory.create("no index");
assertEquals("no index", noIndex.getName());
assertNull(noIndex.getIndex());
Bar index = factory.create("index", 1);
assertEquals("index", index.getName());
assertEquals(1, index.getIndex().intValue());
}
public void testUsingOneConstructor() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(SimpleFactory.class));
}
});
SimpleFactory factory = injector.getInstance(SimpleFactory.class);
Foo noIndex = factory.create("no index");
assertEquals("no index", noIndex.name);
assertNull(noIndex.index);
injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(SimpleFactory2.class));
}
});
SimpleFactory2 factory2 = injector.getInstance(SimpleFactory2.class);
Foo index = factory2.create("index", 1);
assertEquals("index", index.name);
assertEquals(1, index.index.intValue());
}
public void testTooManyMatchingConstructors() {
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder()
.implement(Foo.class, TooManyMatches.class)
.build(SimpleFactory2.class));
}
});
fail("should have failed");
} catch (CreationException expected) {
Asserts.assertContains(expected.getMessage(), "1) " + TooManyMatches.class.getName()
+ " has more than one constructor annotated with @AssistedInject that "
+ "matches the parameters in method " + SimpleFactory2.class.getName());
}
}
public void testNoMatchingConstructorsBecauseTooManyParams() {
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(ComplexFactory.class));
}
});
fail("should have failed");
} catch (CreationException expected) {
Asserts.assertContains(expected.getMessage(), "1) " + Foo.class.getName()
+ " has @AssistedInject constructors, but none of them match the parameters in method "
+ ComplexFactory.class.getName());
}
}
public void testNoMatchingConstrucotsBecauseTooLittleParams() {
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(NullFactory.class));
}
});
fail("should have failed");
} catch (CreationException expected) {
Asserts.assertContains(expected.getMessage(), "1) " + Foo.class.getName()
+ " has @AssistedInject constructors, but none of them match the parameters in method "
+ NullFactory.class.getName());
}
}
public static interface ComplexFactory {
Foo create(String name, int idx, float weight);
}
public static interface NullFactory {
Foo create();
}
public static interface OtherFactory {
Foo create(String name, int idx);
Foo create(int idx, String name);
Foo create(String name);
}
public static interface Factory {
Foo create(String name);
Foo create(String name, int idx);
}
public static interface BarFactory {
Bar create(String name);
Bar create(String name, int idx);
}
public static interface SimpleFactory {
Foo create(String name);
}
public static interface SimpleFactory2 {
Foo create(String name, int idx);
}
public static class TooManyMatches extends Foo {
@AssistedInject TooManyMatches(@Assisted String name, @Assisted int index) {
}
@AssistedInject TooManyMatches(@Assisted int index, @Assisted String name) {
}
}
public static class Foo implements Bar {
private String name;
private Integer index;
Foo() {}
@AssistedInject Foo(@Assisted String name) {
this.name = name;
this.index = null;
}
@AssistedInject Foo(@Assisted String name, @Assisted int index) {
this.name = name;
this.index = index;
}
Foo(String a, String b, String c) {
}
public String getName() { return name; }
public Integer getIndex() { return index; }
}
public static interface Bar {
String getName();
Integer getIndex();
}
public void testDependenciesAndOtherAnnotations() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(FamilyFarmFactory.class));
}
});
FamilyFarmFactory factory = injector.getInstance(FamilyFarmFactory.class);
Farm pops = factory.popsFarm("Pop");
assertEquals("Pop", pops.pop);
assertEquals(null, pops.mom);
Farm moms = factory.momsFarm("Mom");
assertEquals(null, moms.pop);
assertEquals("Mom", moms.mom);
Farm momAndPop = factory.momAndPopsFarm("Mom", "Pop");
assertEquals("Pop", momAndPop.pop);
assertEquals("Mom", momAndPop.mom);
}
public static interface FamilyFarmFactory {
Farm popsFarm(String pop);
Farm momsFarm(@Assisted("mom") String mom);
Farm momAndPopsFarm(@Assisted("mom") String mom, @Assisted("pop") String pop);
}
public static class Farm {
String pop;
String mom;
@AssistedInject Farm(@Assisted String pop, Dog dog) {
this.pop = pop;
}
@AssistedInject Farm(@Assisted("mom") String mom, @Assisted("pop") String pop, Cow cow, Dog dog) {
this.pop = pop;
this.mom = mom;
}
@AssistedInject Farm(@Assisted("mom") String mom, Cow cow) {
this.mom = mom;
}
}
public static class Cow {}
public static class Dog {}
}

View file

@ -0,0 +1,17 @@
package com.google.inject.multibindings;
import junit.framework.Test;
import junit.framework.TestSuite;
public class AllTests {
public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTestSuite(MapBinderTest.class);
suite.addTestSuite(MultibinderTest.class);
suite.addTestSuite(OptionalBinderTest.class);
suite.addTestSuite(RealElementTest.class);
suite.addTestSuite(ProvidesIntoTest.class);
return suite;
}
}

View file

@ -0,0 +1,28 @@
package com.google.inject.multibindings;
import com.google.inject.spi.DefaultBindingTargetVisitor;
class Collector extends DefaultBindingTargetVisitor<Object, Object> implements
MultibindingsTargetVisitor<Object, Object> {
MapBinderBinding<? extends Object> mapbinding;
MultibinderBinding<? extends Object> setbinding;
OptionalBinderBinding<? extends Object> optionalbinding;
@Override
public Object visit(MapBinderBinding<? extends Object> mapbinding) {
this.mapbinding = mapbinding;
return null;
}
@Override
public Object visit(MultibinderBinding<? extends Object> multibinding) {
this.setbinding = multibinding;
return null;
}
@Override
public Object visit(OptionalBinderBinding<? extends Object> optionalbinding) {
this.optionalbinding = optionalbinding;
return null;
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,352 @@
package com.google.inject.multibindings;
import static com.google.inject.Asserts.assertContains;
import static com.google.inject.name.Names.named;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.inject.AbstractModule;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.multibindings.ProvidesIntoOptional.Type;
import com.google.inject.name.Named;
import junit.framework.TestCase;
import java.lang.annotation.Retention;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;
public class ProvidesIntoTest extends TestCase {
public void testAnnotation() throws Exception {
Injector injector = Guice.createInjector(MultibindingsScanner.asModule(), new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoSet
@Named("foo")
String setFoo() { return "foo"; }
@ProvidesIntoSet
@Named("foo")
String setFoo2() { return "foo2"; }
@ProvidesIntoSet
@Named("bar")
String setBar() { return "bar"; }
@ProvidesIntoSet
@Named("bar")
String setBar2() { return "bar2"; }
@ProvidesIntoSet
String setNoAnnotation() { return "na"; }
@ProvidesIntoSet
String setNoAnnotation2() { return "na2"; }
@ProvidesIntoMap
@StringMapKey("fooKey")
@Named("foo")
String mapFoo() { return "foo"; }
@ProvidesIntoMap
@StringMapKey("foo2Key")
@Named("foo")
String mapFoo2() { return "foo2"; }
@ProvidesIntoMap
@ClassMapKey(String.class)
@Named("bar")
String mapBar() { return "bar"; }
@ProvidesIntoMap
@ClassMapKey(Number.class)
@Named("bar")
String mapBar2() { return "bar2"; }
@ProvidesIntoMap
@TestEnumKey(TestEnum.A)
String mapNoAnnotation() { return "na"; }
@ProvidesIntoMap
@TestEnumKey(TestEnum.B)
String mapNoAnnotation2() { return "na2"; }
@ProvidesIntoMap
@WrappedKey(number = 1)
Number wrapped1() { return 11; }
@ProvidesIntoMap
@WrappedKey(number = 2)
Number wrapped2() { return 22; }
@ProvidesIntoOptional(Type.DEFAULT)
@Named("foo")
String optionalDefaultFoo() { return "foo"; }
@ProvidesIntoOptional(Type.ACTUAL)
@Named("foo")
String optionalActualFoo() { return "foo2"; }
@ProvidesIntoOptional(Type.DEFAULT)
@Named("bar")
String optionalDefaultBar() { return "bar"; }
@ProvidesIntoOptional(Type.ACTUAL)
String optionalActualBar() { return "na2"; }
});
Set<String> fooSet = injector.getInstance(new Key<Set<String>>(named("foo")) {});
assertEquals(ImmutableSet.of("foo", "foo2"), fooSet);
Set<String> barSet = injector.getInstance(new Key<Set<String>>(named("bar")) {});
assertEquals(ImmutableSet.of("bar", "bar2"), barSet);
Set<String> noAnnotationSet = injector.getInstance(new Key<Set<String>>() {});
assertEquals(ImmutableSet.of("na", "na2"), noAnnotationSet);
Map<String, String> fooMap =
injector.getInstance(new Key<Map<String, String>>(named("foo")) {});
assertEquals(ImmutableMap.of("fooKey", "foo", "foo2Key", "foo2"), fooMap);
Map<Class<?>, String> barMap =
injector.getInstance(new Key<Map<Class<?>, String>>(named("bar")) {});
assertEquals(ImmutableMap.of(String.class, "bar", Number.class, "bar2"), barMap);
Map<TestEnum, String> noAnnotationMap =
injector.getInstance(new Key<Map<TestEnum, String>>() {});
assertEquals(ImmutableMap.of(TestEnum.A, "na", TestEnum.B, "na2"), noAnnotationMap);
Map<WrappedKey, Number> wrappedMap =
injector.getInstance(new Key<Map<WrappedKey, Number>>() {});
assertEquals(ImmutableMap.of(wrappedKeyFor(1), 11, wrappedKeyFor(2), 22), wrappedMap);
Optional<String> fooOptional =
injector.getInstance(new Key<Optional<String>>(named("foo")) {});
assertEquals("foo2", fooOptional.get());
Optional<String> barOptional =
injector.getInstance(new Key<Optional<String>>(named("bar")) {});
assertEquals("bar", barOptional.get());
Optional<String> noAnnotationOptional =
injector.getInstance(new Key<Optional<String>>() {});
assertEquals("na2", noAnnotationOptional.get());
}
enum TestEnum {
A, B
}
@MapKey(unwrapValue = true)
@Retention(RUNTIME)
@interface TestEnumKey {
TestEnum value();
}
@MapKey(unwrapValue = false)
@Retention(RUNTIME)
@interface WrappedKey {
int number();
}
@SuppressWarnings("unused") @WrappedKey(number=1) private static Object wrappedKey1Holder;
@SuppressWarnings("unused") @WrappedKey(number=2) private static Object wrappedKey2Holder;
WrappedKey wrappedKeyFor(int number) throws Exception {
Field field;
switch (number) {
case 1:
field = ProvidesIntoTest.class.getDeclaredField("wrappedKey1Holder");
break;
case 2:
field = ProvidesIntoTest.class.getDeclaredField("wrappedKey2Holder");
break;
default:
throw new IllegalArgumentException("only 1 or 2 supported");
}
return field.getAnnotation(WrappedKey.class);
}
public void testDoubleScannerIsIgnored() {
Injector injector = Guice.createInjector(
MultibindingsScanner.asModule(),
MultibindingsScanner.asModule(),
new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoSet String provideFoo() { return "foo"; }
}
);
assertEquals(ImmutableSet.of("foo"), injector.getInstance(new Key<Set<String>>() {}));
}
@MapKey(unwrapValue = true)
@Retention(RUNTIME)
@interface ArrayUnwrappedKey {
int[] value();
}
public void testArrayKeys_unwrapValuesTrue() {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoMap @ArrayUnwrappedKey({1, 2}) String provideFoo() { return "foo"; }
};
try {
Guice.createInjector(MultibindingsScanner.asModule(), m);
fail();
} catch (CreationException ce) {
assertEquals(1, ce.getErrorMessages().size());
assertContains(ce.getMessage(),
"Array types are not allowed in a MapKey with unwrapValue=true: "
+ ArrayUnwrappedKey.class.getName(),
"at " + m.getClass().getName() + ".provideFoo(");
}
}
@MapKey(unwrapValue = false)
@Retention(RUNTIME)
@interface ArrayWrappedKey {
int[] number();
}
@SuppressWarnings("unused") @ArrayWrappedKey(number={1, 2}) private static Object arrayWrappedKeyHolder12;
@SuppressWarnings("unused") @ArrayWrappedKey(number={3, 4}) private static Object arrayWrappedKeyHolder34;
ArrayWrappedKey arrayWrappedKeyFor(int number) throws Exception {
Field field;
switch (number) {
case 12:
field = ProvidesIntoTest.class.getDeclaredField("arrayWrappedKeyHolder12");
break;
case 34:
field = ProvidesIntoTest.class.getDeclaredField("arrayWrappedKeyHolder34");
break;
default:
throw new IllegalArgumentException("only 1 or 2 supported");
}
return field.getAnnotation(ArrayWrappedKey.class);
}
public void testArrayKeys_unwrapValuesFalse() throws Exception {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoMap @ArrayWrappedKey(number = {1, 2}) String provideFoo() { return "foo"; }
@ProvidesIntoMap @ArrayWrappedKey(number = {3, 4}) String provideBar() { return "bar"; }
};
Injector injector = Guice.createInjector(MultibindingsScanner.asModule(), m);
Map<ArrayWrappedKey, String> map =
injector.getInstance(new Key<Map<ArrayWrappedKey, String>>() {});
ArrayWrappedKey key12 = arrayWrappedKeyFor(12);
ArrayWrappedKey key34 = arrayWrappedKeyFor(34);
assertEquals("foo", map.get(key12));
assertEquals("bar", map.get(key34));
assertEquals(2, map.size());
}
public void testProvidesIntoSetWithMapKey() {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoSet @TestEnumKey(TestEnum.A) String provideFoo() { return "foo"; }
};
try {
Guice.createInjector(MultibindingsScanner.asModule(), m);
fail();
} catch (CreationException ce) {
assertEquals(1, ce.getErrorMessages().size());
assertContains(ce.getMessage(), "Found a MapKey annotation on non map binding at "
+ m.getClass().getName() + ".provideFoo");
}
}
public void testProvidesIntoOptionalWithMapKey() {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoOptional(Type.ACTUAL)
@TestEnumKey(TestEnum.A)
String provideFoo() {
return "foo";
}
};
try {
Guice.createInjector(MultibindingsScanner.asModule(), m);
fail();
} catch (CreationException ce) {
assertEquals(1, ce.getErrorMessages().size());
assertContains(ce.getMessage(), "Found a MapKey annotation on non map binding at "
+ m.getClass().getName() + ".provideFoo");
}
}
public void testProvidesIntoMapWithoutMapKey() {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoMap String provideFoo() { return "foo"; }
};
try {
Guice.createInjector(MultibindingsScanner.asModule(), m);
fail();
} catch (CreationException ce) {
assertEquals(1, ce.getErrorMessages().size());
assertContains(ce.getMessage(), "No MapKey found for map binding at "
+ m.getClass().getName() + ".provideFoo");
}
}
@MapKey(unwrapValue = true)
@Retention(RUNTIME)
@interface TestEnumKey2 {
TestEnum value();
}
public void testMoreThanOneMapKeyAnnotation() {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoMap
@TestEnumKey(TestEnum.A)
@TestEnumKey2(TestEnum.B)
String provideFoo() {
return "foo";
}
};
try {
Guice.createInjector(MultibindingsScanner.asModule(), m);
fail();
} catch (CreationException ce) {
assertEquals(1, ce.getErrorMessages().size());
assertContains(ce.getMessage(), "Found more than one MapKey annotations on "
+ m.getClass().getName() + ".provideFoo");
}
}
@MapKey(unwrapValue = true)
@Retention(RUNTIME)
@interface MissingValueMethod {
}
public void testMapKeyMissingValueMethod() {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoMap
@MissingValueMethod
String provideFoo() {
return "foo";
}
};
try {
Guice.createInjector(MultibindingsScanner.asModule(), m);
fail();
} catch (CreationException ce) {
assertEquals(1, ce.getErrorMessages().size());
assertContains(ce.getMessage(), "No 'value' method in MapKey with unwrapValue=true: "
+ MissingValueMethod.class.getName());
}
}
}

View file

@ -0,0 +1,37 @@
package com.google.inject.multibindings;
import com.google.inject.multibindings.Element.Type;
import junit.framework.TestCase;
public class RealElementTest extends TestCase {
private Element systemElement;
private RealElement realElement;
@Override protected void setUp() throws Exception {
this.systemElement = Holder.class.getAnnotation(Element.class);
this.realElement = new RealElement("b", Type.MULTIBINDER, "a", 1);
}
public void testEquals() {
assertEquals(systemElement, realElement);
assertEquals(realElement, systemElement);
}
public void testHashCode() {
assertEquals(systemElement.hashCode(), realElement.hashCode());
}
public void testProperties() {
assertEquals("a", realElement.keyType());
assertEquals("b", realElement.setName());
assertEquals(Type.MULTIBINDER, realElement.type());
assertEquals(1, realElement.uniqueId());
}
@Element(keyType = "a", setName = "b", type = Type.MULTIBINDER, uniqueId = 1)
static class Holder {}
}

File diff suppressed because it is too large Load diff