Created
September 11, 2011 23:32
-
-
Save cessationoftime/1210287 to your computer and use it in GitHub Desktop.
Guice FactoryProviders for RoboGuice 1.2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright (C) 2009 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 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 RoboFactoryModuleBuilder} that creates the | |
* factory: | |
* | |
* <pre>install(new RoboFactoryModuleBuilder() | |
* .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 RoboFactoryModuleBuilder() | |
* .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 RoboFactoryModuleBuilder().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 RoboFactoryModuleBuilder() | |
* .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. | |
* | |
* @since 3.0 | |
* @author [email protected] (Peter Schmitt) | |
*/ | |
public final class RoboFactoryModuleBuilder { | |
private final BindingCollector bindings = new BindingCollector(); | |
/** | |
* See the factory configuration examples at {@link RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder implement(Class<T> source, Class<? extends T> target) { | |
return implement(source, TypeLiteral.get(target)); | |
} | |
/** | |
* See the factory configuration examples at {@link RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder implement(Class<T> source, TypeLiteral<? extends T> target) { | |
return implement(TypeLiteral.get(source), target); | |
} | |
/** | |
* See the factory configuration examples at {@link RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder implement(TypeLiteral<T> source, Class<? extends T> target) { | |
return implement(source, TypeLiteral.get(target)); | |
} | |
/** | |
* See the factory configuration examples at {@link RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder implement(TypeLiteral<T> source, | |
TypeLiteral<? extends T> target) { | |
return implement(Key.get(source), target); | |
} | |
/** | |
* See the factory configuration examples at {@link RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder implement(Class<T> source, Annotation annotation, | |
Class<? extends T> target) { | |
return implement(source, annotation, TypeLiteral.get(target)); | |
} | |
/** | |
* See the factory configuration examples at {@link RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder implement(Class<T> source, Annotation annotation, | |
TypeLiteral<? extends T> target) { | |
return implement(TypeLiteral.get(source), annotation, target); | |
} | |
/** | |
* See the factory configuration examples at {@link RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder implement(TypeLiteral<T> source, Annotation annotation, | |
Class<? extends T> target) { | |
return implement(source, annotation, TypeLiteral.get(target)); | |
} | |
/** | |
* See the factory configuration examples at {@link RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder implement(TypeLiteral<T> source, Annotation annotation, | |
TypeLiteral<? extends T> target) { | |
return implement(Key.get(source, annotation), target); | |
} | |
/** | |
* See the factory configuration examples at {@link RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder 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 RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder 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 RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder 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 RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder 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 RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder implement(Key<T> source, Class<? extends T> target) { | |
return implement(source, TypeLiteral.get(target)); | |
} | |
/** | |
* See the factory configuration examples at {@link RoboFactoryModuleBuilder}. | |
*/ | |
public <T> RoboFactoryModuleBuilder implement(Key<T> source, TypeLiteral<? extends T> target) { | |
bindings.addBinding(source, target); | |
return this; | |
} | |
/** | |
* See the factory configuration examples at {@link RoboFactoryModuleBuilder}. | |
*/ | |
public <F> Module build(Class<F> factoryInterface) { | |
return build(TypeLiteral.get(factoryInterface)); | |
} | |
/** | |
* See the factory configuration examples at {@link RoboFactoryModuleBuilder}. | |
*/ | |
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 RoboFactoryProvider2<F>(factoryInterface, bindings); | |
bind(factoryInterface).toProvider(provider); | |
} | |
}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright (C) 2007 Google Inc. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.google.inject.assistedinject; | |
import static com.google.inject.internal.Annotations.getKey; | |
import android.content.Context; | |
import com.google.common.collect.ImmutableMap; | |
import com.google.common.collect.ImmutableSet; | |
import com.google.common.collect.Lists; | |
import com.google.common.collect.Maps; | |
import com.google.inject.ConfigurationException; | |
import com.google.inject.Inject; | |
import com.google.inject.Injector; | |
import com.google.inject.Key; | |
import com.google.inject.Provider; | |
import com.google.inject.TypeLiteral; | |
import com.google.inject.internal.BytecodeGen; | |
import com.google.inject.internal.Errors; | |
import com.google.inject.internal.ErrorsException; | |
import com.google.inject.spi.Dependency; | |
import com.google.inject.spi.HasDependencies; | |
import com.google.inject.spi.Message; | |
import java.lang.annotation.Annotation; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.InvocationHandler; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Proxy; | |
import java.lang.reflect.Type; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import roboguice.inject.ContextScope; | |
/** | |
* <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( | |
* RoboFactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));</pre> | |
* As a side-effect of this binding, Guice will inject the factory to initialize it for use. The | |
* factory cannot be used until the injector has been initialized. | |
* | |
* <h3>Using the factory</h3> | |
* Inject your factory into your application classes. When you use the factory, your arguments | |
* will be combined with values from the injector to construct an instance. | |
* <pre>public class PaymentAction { | |
* {@literal @}Inject private PaymentFactory paymentFactory; | |
* | |
* public void doPayment(Money amount) { | |
* Payment payment = paymentFactory.create(new Date(), amount); | |
* payment.apply(); | |
* } | |
* }</pre> | |
* | |
* <h3>Making parameter types distinct</h3> | |
* The types of the factory method's parameters must be distinct. To use multiple parameters of | |
* the same type, use a named {@literal @}{@link Assisted} annotation to disambiguate the | |
* parameters. The names must be applied to the factory method's parameters: | |
* | |
* <pre>public interface PaymentFactory { | |
* Payment create( | |
* <strong>{@literal @}Assisted("startDate")</strong> Date startDate, | |
* <strong>{@literal @}Assisted("dueDate")</strong> Date dueDate, | |
* Money amount); | |
* } </pre> | |
* ...and to the concrete type's constructor parameters: | |
* <pre>public class RealPayment implements Payment { | |
* {@literal @}Inject | |
* public RealPayment( | |
* CreditService creditService, | |
* AuthService authService, | |
* <strong>{@literal @}Assisted("startDate")</strong> Date startDate, | |
* <strong>{@literal @}Assisted("dueDate")</strong> Date dueDate, | |
* <strong>{@literal @}Assisted</strong> Money amount) { | |
* ... | |
* } | |
* }</pre> | |
* | |
* <h3>Values are created by Guice</h3> | |
* Returned factories use child injectors to create values. The values are eligible for method | |
* interception. In addition, {@literal @}{@literal Inject} members will be injected before they are | |
* returned. | |
* | |
* <h3>Backwards compatibility using {@literal @}AssistedInject</h3> | |
* Instead of the {@literal @}Inject annotation, you may annotate the constructed classes with | |
* {@literal @}{@link AssistedInject}. This triggers a limited backwards-compatability mode. | |
* | |
* <p>Instead of matching factory method arguments to constructor parameters using their names, the | |
* <strong>parameters are matched by their order</strong>. The first factory method argument is | |
* used for the first {@literal @}Assisted constructor parameter, etc.. Annotation names have no | |
* effect. | |
* | |
* <p>Returned values are <strong>not created by Guice</strong>. These types are not eligible for | |
* method interception. They do receive post-construction member injection. | |
* | |
* @param <F> The factory interface | |
* | |
* @author [email protected] (Jerome Mourits) | |
* @author [email protected] (Jesse Wilson) | |
* @author [email protected] (Daniel Martin) | |
* | |
* @deprecated use {@link FactoryModuleBuilder} instead. | |
*/ | |
@Deprecated | |
public class RoboFactoryProvider<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 RoboFactoryProvider2. | |
*/ | |
private Injector injector; | |
private final TypeLiteral<F> factoryType; | |
private final Map<Method, AssistedConstructor<?>> factoryMethodToConstructor; | |
public static <F> Provider<F> newFactory(Class<F> factoryType, Class<?> implementationType){ | |
return newFactory(TypeLiteral.get(factoryType), TypeLiteral.get(implementationType)); | |
} | |
public static <F> Provider<F> newFactory( | |
TypeLiteral<F> factoryType, TypeLiteral<?> implementationType) { | |
Map<Method, AssistedConstructor<?>> factoryMethodToConstructor | |
= createMethodMapping(factoryType, implementationType); | |
if (!factoryMethodToConstructor.isEmpty()) { | |
return new RoboFactoryProvider<F>(factoryType, 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); | |
if (implementationType != null) { | |
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 RoboFactoryProvider2<F>(Key.get(factoryType), collector); | |
} | |
} | |
private RoboFactoryProvider(TypeLiteral<F> factoryType, | |
Map<Method, AssistedConstructor<?>> factoryMethodToConstructor) { | |
this.factoryType = factoryType; | |
this.factoryMethodToConstructor = factoryMethodToConstructor; | |
checkDeclaredExceptionsMatch(); | |
} | |
@Inject | |
void setInjectorAndCheckUnboundParametersAreInjectable(Injector injector) { | |
this.injector = injector; | |
for (AssistedConstructor<?> c : factoryMethodToConstructor.values()) { | |
for (Parameter p : c.getAllParameters()) { | |
if(!p.isProvidedByFactory() && !paramCanBeInjected(p, injector)) { | |
// this is lame - we're not using the proper mechanism to add an | |
// error to the injector. Throughout this class we throw exceptions | |
// to add errors, which isn't really the best way in Guice | |
throw newConfigurationException("Parameter of type '%s' is not injectable or annotated " | |
+ "with @Assisted for Constructor '%s'", p, c); | |
} | |
} | |
} | |
} | |
private void checkDeclaredExceptionsMatch() { | |
for (Map.Entry<Method, AssistedConstructor<?>> entry : factoryMethodToConstructor.entrySet()) { | |
for (Class<?> constructorException : entry.getValue().getDeclaredExceptions()) { | |
if (!isConstructorExceptionCompatibleWithFactoryExeception( | |
constructorException, entry.getKey().getExceptionTypes())) { | |
throw newConfigurationException("Constructor %s declares an exception, but no compatible " | |
+ "exception is thrown by the factory method %s", entry.getValue(), entry.getKey()); | |
} | |
} | |
} | |
} | |
private boolean isConstructorExceptionCompatibleWithFactoryExeception( | |
Class<?> constructorException, Class<?>[] factoryExceptions) { | |
for (Class<?> factoryException : factoryExceptions) { | |
if (factoryException.isAssignableFrom(constructorException)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
private boolean paramCanBeInjected(Parameter parameter, Injector injector) { | |
return parameter.isBound(injector); | |
} | |
private static Map<Method, AssistedConstructor<?>> createMethodMapping( | |
TypeLiteral<?> factoryType, TypeLiteral<?> implementationType) { | |
List<AssistedConstructor<?>> constructors = Lists.newArrayList(); | |
for (Constructor<?> constructor : implementationType.getRawType().getDeclaredConstructors()) { | |
if (constructor.getAnnotation(AssistedInject.class) != null) { | |
@SuppressWarnings("unchecked") // the constructor type and implementation type agree | |
AssistedConstructor assistedConstructor = new AssistedConstructor( | |
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; | |
} | |
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 { | |
Context context = null; | |
if (Context.class.isInstance(creationArgs[0])) { | |
context = (Context)creationArgs[0]; | |
} else { | |
String classNames = ""; | |
for(Object o : creationArgs) { | |
if (o==null) classNames = "null " + classNames; | |
else classNames = o.getClass().getName() + " " + classNames; | |
} | |
throw new RuntimeException("RoboFactory for: " + method.getReturnType().getName() + " must be supplied with android.content.Context to the factory creation methods " + ", args: " + classNames); | |
} | |
ContextScope scope = injector.getInstance(ContextScope.class); | |
// pass methods from Object.class to the proxy | |
if (method.getDeclaringClass().equals(Object.class)) { | |
synchronized (ContextScope.class) { | |
scope.enter(context); | |
try { | |
return method.invoke(this, creationArgs); | |
} finally { | |
scope.exit(context); | |
} | |
} | |
} | |
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) factoryType.getRawType(); | |
return factoryRawType.cast(Proxy.newProxyInstance(BytecodeGen.getClassLoader(factoryRawType), | |
new Class[] { factoryRawType }, invocationHandler)); | |
} | |
private static ConfigurationException newConfigurationException(String format, Object... args) { | |
return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args)))); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright (C) 2008 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.common.base.Preconditions.checkState; | |
import static com.google.common.collect.Iterables.getOnlyElement; | |
import android.content.Context; | |
import com.google.common.base.Objects; | |
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.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.BytecodeGen; | |
import com.google.inject.internal.Errors; | |
import com.google.inject.internal.ErrorsException; | |
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.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 roboguice.inject.ContextScope; | |
/** | |
* The newer implementation of factory provider. This implementation uses a child injector to | |
* create values. | |
* | |
* @author [email protected] (Jesse Wilson) | |
* @author [email protected] (Daniel Martin) | |
* @author [email protected] (Peter Schmitt) | |
* @author [email protected] (Sam Berlin) | |
*/ | |
final class RoboFactoryProvider2 <F> implements InvocationHandler, | |
ProviderWithExtensionVisitor<F>, HasDependencies, AssistedInjectBinding<F> { | |
/** 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().equals(""); | |
} | |
@Override public int hashCode() { | |
return 127 * "value".hashCode() ^ "".hashCode(); | |
} | |
@Override public String toString() { | |
return "@" + Assisted.class.getName() + "(value=)"; | |
} | |
}; | |
/** 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 #validForOptimizedAssistedInject} 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; | |
} | |
} | |
/** Mapping from method to the data about how the method will be assisted. */ | |
private final ImmutableMap<Method, AssistData> assistDataByMethod; | |
/** the hosting injector, or null if we haven't been initialized yet */ | |
private Injector injector; | |
/** the factory interface, implemented and provided */ | |
private final F factory; | |
/** The key that this is bound to. */ | |
private final Key<F> factoryKey; | |
/** | |
* @param factoryType a Java interface that defines one or more create methods. | |
* @param collector binding configuration that maps method return types to | |
* implementation types. | |
*/ | |
RoboFactoryProvider2(Key<F> factoryKey, BindingCollector collector) { | |
this.factoryKey = factoryKey; | |
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) factoryType.getRawType(); | |
try { | |
if(!factoryRawType.isInterface()) { | |
throw errors.addMessage("%s must be an interface.", factoryRawType).toException(); | |
} | |
ImmutableMap.Builder<Method, AssistData> assistDataBuilder = ImmutableMap.builder(); | |
// TODO: also grab methods from superinterfaces | |
for (Method method : factoryRawType.getMethods()) { | |
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(); | |
} | |
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)) { | |
ImmutableList.Builder<ThreadLocalProvider> providerListBuilder = ImmutableList.builder(); | |
for(int i = 0; i < params.size(); i++) { | |
providerListBuilder.add(new ThreadLocalProvider()); | |
} | |
providers = providerListBuilder.build(); | |
optimized = true; | |
} | |
assistDataBuilder.put(method, | |
new AssistData(constructor, returnType, immutableParamList, implementation, | |
method, removeAssistedDeps(deps), optimized, providers)); | |
} | |
// If we generated any errors (from finding matching constructors, for instance), throw an exception. | |
if(errors.hasErrors()) { | |
throw errors.toException(); | |
} | |
assistDataByMethod = assistDataBuilder.build(); | |
} catch (ErrorsException e) { | |
throw new ConfigurationException(e.getErrors().getMessages()); | |
} | |
factory = factoryRawType.cast(Proxy.newProxyInstance(BytecodeGen.getClassLoader(factoryRawType), | |
new Class[] { factoryRawType }, this)); | |
} | |
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 | |
@SuppressWarnings("unchecked") | |
public Collection<AssistedMethod> getAssistedMethods() { | |
return (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 InjectionPoint findMatchingConstructorInjectionPoint( | |
Method method, Key<?> returnType, TypeLiteral<?> 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 (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)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) { | |
for (Dependency<?> dep : dependencies) { | |
if (isInjectorOrAssistedProvider(dep)) { | |
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(RoboFactoryProvider2.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<?> assistedReturnType = Key.get(returnType.getTypeLiteral(), Assisted.class); | |
Module assistedModule = new AbstractModule() { | |
@Override @SuppressWarnings("unchecked") // 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(assistedReturnType) | |
.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(assistedReturnType); | |
// 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 { | |
Context context = null; | |
if (Context.class.isInstance(args[0])) { | |
context = (Context)args[0]; | |
} else { | |
String classNames = ""; | |
for(Object o : args) { | |
if (o==null) classNames = "null " + classNames; | |
else classNames = o.getClass().getName() + " " + classNames; | |
} | |
throw new RuntimeException("RoboFactory for: " + method.getReturnType().getName() + " must be have android.content.Context as arg[0], args: " + classNames); | |
} | |
ContextScope scope = injector.getInstance(ContextScope.class); | |
if (method.getDeclaringClass() == Object.class) { | |
synchronized (ContextScope.class) { | |
scope.enter(context); | |
try { | |
return method.invoke(this, args); | |
} finally { | |
scope.exit(context); | |
} | |
} | |
} | |
AssistData data = assistDataByMethod.get(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++]); | |
} | |
synchronized (ContextScope.class) { | |
scope.enter(context); | |
try { | |
return provider.get(); | |
} finally { | |
scope.exit(context); | |
} | |
} | |
} 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 boolean equals(Object o) { | |
return o == this || o == factory; | |
} | |
/** 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; | |
} | |
public static boolean isInnerClass(Class<?> clazz) { | |
return !Modifier.isStatic(clazz.getModifiers()) | |
&& clazz.getEnclosingClass() != null; | |
} | |
// 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.)"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment