added assistedinject and multibindings extension
This commit is contained in:
parent
18da30f945
commit
3d4887f08c
42 changed files with 13209 additions and 0 deletions
26
src/main/java/com/google/inject/assistedinject/Assisted.java
Normal file
26
src/main/java/com/google/inject/assistedinject/Assisted.java
Normal 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 "";
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
146
src/main/java/com/google/inject/assistedinject/Parameter.java
Normal file
146
src/main/java/com/google/inject/assistedinject/Parameter.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
31
src/main/java/com/google/inject/multibindings/Element.java
Normal file
31
src/main/java/com/google/inject/multibindings/Element.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
167
src/main/java/com/google/inject/multibindings/Indexer.java
Normal file
167
src/main/java/com/google/inject/multibindings/Indexer.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
873
src/main/java/com/google/inject/multibindings/MapBinder.java
Normal file
873
src/main/java/com/google/inject/multibindings/MapBinder.java
Normal 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<String, Snack> 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<String, Snack> 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<String, Provider<Snack>> 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<K, V>, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<K, V>, Map
|
||||||
|
* <K, Provider<V>>, Map<K, Set<V>>, Map<K, Set<
|
||||||
|
* Provider<V>>, and even Set<Map.Entry<K, Provider<V>>), a
|
||||||
|
* MapBinderBinding exists only on the Binding associated with the Map<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<Map<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<String, Snack></code>, then this will always return a
|
||||||
|
* <code>TypeLiteral<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<String, Snack></code>, then this will always return a
|
||||||
|
* <code>TypeLiteral<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<String, Snack></code>, then this will always return a list of type
|
||||||
|
* <code>List<Map.Entry<String, Binding<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);
|
||||||
|
}
|
42
src/main/java/com/google/inject/multibindings/MapKey.java
Normal file
42
src/main/java/com/google/inject/multibindings/MapKey.java
Normal 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;
|
||||||
|
}
|
575
src/main/java/com/google/inject/multibindings/Multibinder.java
Normal file
575
src/main/java/com/google/inject/multibindings/Multibinder.java
Normal 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<Snack> 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<Snack> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Set<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<String></code>, then this will always return a
|
||||||
|
* <code>TypeLiteral<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<String></code>, then this will always return a list of type
|
||||||
|
* <code>List<Binding<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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String></code>, then this will always return a
|
||||||
|
* <code>Binding<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<String></code>, then this will always return a
|
||||||
|
* <code>Binding<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);
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {}
|
||||||
|
|
||||||
|
}
|
17
src/test/java/com/google/inject/multibindings/AllTests.java
Normal file
17
src/test/java/com/google/inject/multibindings/AllTests.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
28
src/test/java/com/google/inject/multibindings/Collector.java
Normal file
28
src/test/java/com/google/inject/multibindings/Collector.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
1013
src/test/java/com/google/inject/multibindings/MapBinderTest.java
Normal file
1013
src/test/java/com/google/inject/multibindings/MapBinderTest.java
Normal file
File diff suppressed because it is too large
Load diff
1202
src/test/java/com/google/inject/multibindings/MultibinderTest.java
Normal file
1202
src/test/java/com/google/inject/multibindings/MultibinderTest.java
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {}
|
||||||
|
|
||||||
|
}
|
1099
src/test/java/com/google/inject/multibindings/SpiUtils.java
Normal file
1099
src/test/java/com/google/inject/multibindings/SpiUtils.java
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue