Author: limpbizkit
Date: Mon Dec 1 22:15:57 2008
New Revision: 714
Added:
trunk/extensions/assistedinject/src/com/google/inject/assistedinject/Factories.java
trunk/extensions/assistedinject/test/com/google/inject/assistedinject/FactoriesTest.java
Modified:
trunk/extensions/assistedinject/src/com/google/inject/assistedinject/Assisted.java
trunk/src/com/google/inject/internal/Annotations.java
trunk/src/com/google/inject/internal/Errors.java
Log:
AssistedInject Deluxe.
This introduces a new static utility class, Factories, that can be used to
build factories. It behaves very similarly to FactoryProvider, with a few
minor differences:
- matching is by @Assisted("name") rather than position
- AOP is supported
- factory params can be injected anywhere - as parameters, Providers,
method arguments, indirect dependencies, etc.
- cglib is used internally instead of Proxy. This allows the factory to be
injected directly
Modified:
trunk/extensions/assistedinject/src/com/google/inject/assistedinject/Assisted.java
==============================================================================
---
trunk/extensions/assistedinject/src/com/google/inject/assistedinject/Assisted.java
(original)
+++
trunk/extensions/assistedinject/src/com/google/inject/assistedinject/Assisted.java
Mon Dec 1 22:15:57 2008
@@ -16,20 +16,21 @@
package com.google.inject.assistedinject;
+import com.google.inject.BindingAnnotation;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
/**
- * The [EMAIL PROTECTED] @Assisted} annotation should be used on paramters
within
- * a constructor annotated with [EMAIL PROTECTED] @AssistedInject}. The
annotation
- * indicates that the parameter will be supplied through a factory
- * method (the parameter will not be injected by Guice).
- *
+ * Annotates an injected parameter or field whose value comes from an
argument to a factory method.
+ *
* @author [EMAIL PROTECTED] (Jerome Mourits)
* @author [EMAIL PROTECTED] (Jesse Wilson)
*/
[EMAIL PROTECTED]({PARAMETER})
[EMAIL PROTECTED](RUNTIME)
-public @interface Assisted {}
\ No newline at end of file
[EMAIL PROTECTED] @Target({ FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
+public @interface Assisted {
+ String value() default "";
+}
\ No newline at end of file
Added:
trunk/extensions/assistedinject/src/com/google/inject/assistedinject/Factories.java
==============================================================================
--- (empty file)
+++
trunk/extensions/assistedinject/src/com/google/inject/assistedinject/Factories.java
Mon Dec 1 22:15:57 2008
@@ -0,0 +1,360 @@
+/**
+ * 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 com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import static com.google.common.collect.Iterables.getOnlyElement;
+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.TypeLiteral;
+import static com.google.inject.internal.Annotations.getKey;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.ErrorsException;
+import com.google.inject.spi.Message;
+import com.google.inject.util.Providers;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import net.sf.cglib.proxy.Enhancer;
+import net.sf.cglib.proxy.InvocationHandler;
+
+/**
+ * Static utility methods for creating and working with factory interfaces.
+ *
+ * @author [EMAIL PROTECTED] (Jerome Mourits)
+ * @author [EMAIL PROTECTED] (Jesse Wilson)
+ * @author [EMAIL PROTECTED] (Daniel Martin)
+ */
+public final class Factories {
+ private Factories() {}
+
+ private static final Class[] ONLY_RIH = { RealInvocationHandler.class };
+
+ static final Assisted DEFAULT_ASSISTED = 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=)";
+ }
+ };
+
+ /**
+ * Returns a factory that combines caller-provided parameters with
injector-provided values when
+ * constructing objects.
+ *
+ * <h3>Defining a Factory</h3>
+ * [EMAIL PROTECTED] factoryInterface} is an interface whose methods return
the
constructed type, or 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>. You may include multiple
factory methods in the
+ * same interface but they must all construct the same type.
+ *
+ * <h3>Creating a type that accepts factory parameters</h3>
+ * [EMAIL PROTECTED] constructedType} is a concrete class with an [EMAIL
PROTECTED]
@[EMAIL PROTECTED] Inject}-annotated
+ * constructor. In addition to injector-provided parameters, the
constructor should have
+ * parameters that match each of the factory method's parameters. Each
factory-provided parameter
+ * requires an [EMAIL PROTECTED] @[EMAIL PROTECTED] Assisted} annotation.
This serves to
document that the parameter
+ * is not bound in the injector.
+ *
+ * <pre>
+ * public class RealPayment implements Payment {
+ * [EMAIL PROTECTED] @}Inject
+ * public RealPayment(
+ * CreditService creditService,
+ * AuthService authService,
+ * <strong>[EMAIL PROTECTED] @}Assisted Date startDate</strong>,
+ * <strong>[EMAIL PROTECTED] @}Assisted Money amount</strong>) {
+ * ...
+ * }
+ * }</pre>
+ *
+ * <h3>Configuring factories</h3>
+ * In your [EMAIL PROTECTED] com.google.inject.Module module}, bind the
factory
interface to the returned
+ * factory:
+ *
+ * <pre>
+ * bind(PaymentFactory.class).toInstance(
+ * Factories.create(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 it 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 produce a concrete
instance.
+ *
+ * <pre>
+ * public class PaymentAction {
+ * [EMAIL PROTECTED] @}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 [EMAIL PROTECTED] @[EMAIL PROTECTED] Assisted}
annotation to
disambiguate the
+ * parameters. The names must be applied to the factory method's
parameters:
+ *
+ * <pre>
+ * public interface PaymentFactory {
+ * Payment create(
+ * <strong>[EMAIL PROTECTED] @}Assisted("startDate")</strong> Date
startDate,
+ * <strong>[EMAIL PROTECTED] @}Assisted("dueDate")</strong> Date
dueDate,
+ * Money amount);
+ * } </pre>
+ * ...and to the concrete type's constructor parameters:
+ * <pre>
+ * public class RealPayment implements Payment {
+ * [EMAIL PROTECTED] @}Inject
+ * public RealPayment(
+ * CreditService creditService,
+ * AuthService authService,
+ * <strong>[EMAIL PROTECTED] @}Assisted("startDate")</strong> Date
startDate,
+ * <strong>[EMAIL PROTECTED] @}Assisted("dueDate")</strong> Date
dueDate,
+ * <strong>[EMAIL PROTECTED] @}Assisted</strong> Money amount) {
+ * ...
+ * }
+ * }</pre>
+ *
+ * <h3>MethodInterceptor support</h3>
+ * Returned factories delegate to the injector to construct returned
values. The values are
+ * eligible for method interception.
+ *
+ * @param factoryInterface a Java interface that defines one or more
create methods.
+ * @param constructedType a concrete type that is assignable to the
return types of all factory
+ * methods.
+ */
+ public static <F> F create(Class<F> factoryInterface, Class<?>
constructedType) {
+ RealInvocationHandler<F> invocationHandler
+ = new RealInvocationHandler<F>(factoryInterface,
Key.get(constructedType));
+ Enhancer enhancer = new Enhancer();
+ enhancer.setSuperclass(Base.class);
+ enhancer.setInterfaces(new Class[] { factoryInterface });
+ enhancer.setCallback(invocationHandler);
+ return factoryInterface.cast(enhancer.create(ONLY_RIH, new Object[] {
invocationHandler }));
+ }
+
+ /**
+ * Generated factories extend this class, which gives us a hook to get
injected by Guice. Normal
+ * Java proxies can't be injected, so we use cglib.
+ */
+ private static class Base {
+ private final RealInvocationHandler<?> invocationHandler;
+
+ protected Base(RealInvocationHandler<?> invocationHandler) {
+ this.invocationHandler = invocationHandler;
+ }
+
+ @SuppressWarnings("unused")
+ @Inject private void initialize(Injector injector) {
+ invocationHandler.initialize(injector);
+ }
+ }
+
+ // TODO: also grab methods from superinterfaces
+
+ private static class RealInvocationHandler<F> implements
InvocationHandler {
+ /** the produced type, or null if all methods return concrete types */
+ private final Key<?> producedType;
+ private final ImmutableMap<Method, Key<?>> returnTypesByMethod;
+ private final ImmutableMultimap<Method, Key<?>> paramTypes;
+
+ /** the hosting injector, or null if we haven't been initialized yet */
+ private Injector injector;
+
+ private RealInvocationHandler(Class<F> factoryType, Key<?>
producedType) {
+ this.producedType = producedType;
+
+ Errors errors = new Errors();
+ try {
+ ImmutableMap.Builder<Method, Key<?>> returnTypesBuilder =
ImmutableMap.builder();
+ ImmutableMultimap.Builder<Method, Key<?>> paramTypesBuilder =
ImmutableMultimap.builder();
+ for (Method method : factoryType.getMethods()) {
+ Key<?> returnType =
getKey(TypeLiteral.get(method.getGenericReturnType()),
+ method, method.getAnnotations(), errors);
+ returnTypesBuilder.put(method, returnType);
+ Type[] params = method.getGenericParameterTypes();
+ Annotation[][] paramAnnotations =
method.getParameterAnnotations();
+ int p = 0;
+ for (Type param : params) {
+ Key<?> paramKey = getKey(TypeLiteral.get(param), method,
paramAnnotations[p++], errors);
+ paramTypesBuilder.put(method, assistKey(method, paramKey,
errors));
+ }
+ }
+ returnTypesByMethod = returnTypesBuilder.build();
+ paramTypes = paramTypesBuilder.build();
+ } catch (ErrorsException e) {
+ throw new ConfigurationException(e.getErrors().getMessages());
+ }
+ }
+
+ /**
+ * Returns a key similar to [EMAIL PROTECTED] key}, but with an [EMAIL
PROTECTED]
@}Assisted binding annotation.
+ * This fails if another binding annotation is clobbered in the
process. If the key already has
+ * the [EMAIL PROTECTED] @}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_ASSISTED);
+ } 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.
+ */
+ void initialize(Injector injector) {
+ if (this.injector != null) {
+ throw new ConfigurationException(ImmutableList.of(new
Message(Factories.class,
+ "Factories.create() factories may only be used in one
Injector!")));
+ }
+
+ this.injector = injector;
+
+ for (Method method : returnTypesByMethod.keySet()) {
+ Object[] args = new Object[method.getParameterTypes().length];
+ Arrays.fill(args, "dummy object for validating Factories");
+ getBindingFromNewInjector(method, args); // 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) {
+ checkState(injector != null,
+ "Factories.create() factories cannot be used until they're
initialized by Guice.");
+
+ final Key<?> returnType = returnTypesByMethod.get(method);
+
+ Module assistedModule = new AbstractModule() {
+ @SuppressWarnings("unchecked") // raw keys are necessary for the
args array and return value
+ protected void configure() {
+ Binder binder = binder().withSource(method);
+
+ int p = 0;
+ for (Key<?> paramKey : paramTypes.get(method)) {
+ // Wrap in a Provider to cover null, and to prevent Guice from
injecting the parameter
+ binder.bind((Key)
paramKey).toProvider(Providers.of(args[p++]));
+ }
+
+ if (producedType != null && !returnType.equals(producedType)) {
+ binder.bind(returnType).to((Key) producedType);
+ } else {
+ binder.bind(returnType);
+ }
+ }
+ };
+
+ Injector forCreate = injector.createChildInjector(assistedModule);
+ return forCreate.getBinding(returnType);
+ }
+
+ /**
+ * 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 (method.getDeclaringClass() == Object.class) {
+ return method.invoke(this, args);
+ }
+
+ Provider<?> provider = getBindingFromNewInjector(method,
args).getProvider();
+ try {
+ 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;
+ }
+ }
+
+ @Override public String toString() {
+ return "Factory";
+ }
+
+ @Override public boolean equals(Object o) {
+ // this equals() is wacky; we pretend it's defined on the Proxy
object rather than here
+ return o instanceof Base
+ && ((Base) o).invocationHandler == this;
+ }
+ }
+
+ /** Returns true if [EMAIL PROTECTED] thrown} can be thrown by [EMAIL
PROTECTED] 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;
+ }
+}
Added:
trunk/extensions/assistedinject/test/com/google/inject/assistedinject/FactoriesTest.java
==============================================================================
--- (empty file)
+++
trunk/extensions/assistedinject/test/com/google/inject/assistedinject/FactoriesTest.java
Mon Dec 1 22:15:57 2008
@@ -0,0 +1,629 @@
+/**
+ * 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 com.google.inject.AbstractModule;
+import static com.google.inject.Asserts.assertContains;
+import static com.google.inject.Asserts.assertEqualsBothWays;
+import com.google.inject.CreationException;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+import com.google.inject.matcher.Matchers;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+import java.awt.Color;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import junit.framework.TestCase;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+public class FactoriesTest extends TestCase {
+
+ public void testAssistedFactory() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(Double.class).toInstance(5.0d);
+ bind(ColoredCarFactory.class).toInstance(
+ Factories.create(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 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).toInstance(
+ Factories.create(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;
+
+ @Inject
+ public Mustang(double engineSize, @Assisted Color color) {
+ this.engineSize = engineSize;
+ this.color = color;
+ }
+
+ public String drive() {
+ return "vroom!";
+ }
+ }
+
+ public static class Camaro implements Car {
+ private final int horsePower;
+ private final int modelYear;
+ private final Color color;
+
+ @Inject
+ 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);
+ }
+
+ public void testFactoryWithMultipleMethods() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(float.class).toInstance(140f);
+ bind(SummerCarFactory.class).toInstance(
+ Factories.create(SummerCarFactory.class, Corvette.class));
+ }
+ });
+
+ SummerCarFactory carFactory =
injector.getInstance(SummerCarFactory.class);
+
+ 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;
+
+ public Corvette(Color color, boolean isConvertable) {
+ throw new IllegalStateException("Not an @AssistedInject
constructor");
+ }
+
+ @Inject
+ public Corvette(@Assisted Color color, Float maxMph, @Assisted boolean
isConvertable) {
+ this.isConvertable = isConvertable;
+ this.color = color;
+ this.maxMph = maxMph;
+ }
+ }
+
+ public void testConstructorDoesntNeedAllFactoryMethodArguments() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(SummerCarFactory.class).toInstance(
+ Factories.create(SummerCarFactory.class, Beetle.class));
+ }
+ });
+ SummerCarFactory factory =
injector.getInstance(SummerCarFactory.class);
+
+ Beetle beetle = (Beetle) factory.create(Color.RED, true);
+ assertSame(Color.RED, beetle.color);
+ }
+
+ public static class Beetle implements Car {
+ private final Color color;
+ @Inject
+ public Beetle(@Assisted Color color) {
+ this.color = color;
+ }
+ }
+
+ 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).toInstance(
+ Factories.create(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;
+
+ @Inject
+ 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).toInstance(
+ Factories.create(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;
+
+ @Inject
+ 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).toInstance(
+ Factories.create(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;
+
+ @Inject
+ 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).toInstance(
+ Factories.create(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;
+
+ @Inject
+ public Z(Provider<Set<String>> manufacturersProvider, @Assisted Color
color) {
+ this.manufacturersProvider = manufacturersProvider;
+ this.color = color;
+ }
+ }
+
+ public static class Prius implements Car {
+ @SuppressWarnings("unused")
+ final Color color;
+
+ @Inject
+ private Prius(@Assisted Color color) {
+ this.color = color;
+ }
+ }
+
+ public void testAssistInjectionInNonPublicConstructor() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(ColoredCarFactory.class).toInstance(
+ Factories.create(ColoredCarFactory.class, Prius.class));
+ }
+ });
+ Car car =
injector.getInstance(ColoredCarFactory.class).create(Color.ORANGE);
+ }
+
+ public static class ExplodingCar implements Car {
+ @Inject
+ public ExplodingCar(@Assisted Color color) {
+ throw new IllegalStateException("kaboom!");
+ }
+ }
+
+ public void testExceptionDuringConstruction() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(ColoredCarFactory.class).toInstance(
+ Factories.create(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 {
+ @Inject
+ public DefectiveCar() throws ExplosionException, FireException {
+ 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 interface CorrectDefectiveCarFactory {
+ Car createCar() throws FireException, ExplosionException;
+ }
+
+ public void testConstructorExceptionsAreThrownByFactory() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(CorrectDefectiveCarFactory.class).toInstance(
+ Factories.create(CorrectDefectiveCarFactory.class,
DefectiveCar.class));
+ }
+ });
+ try {
+ injector.getInstance(CorrectDefectiveCarFactory.class).createCar();
+ fail();
+ } catch (FireException e) {
+ fail();
+ } catch (ExplosionException expected) {
+ }
+ }
+
+ public static class MultipleConstructorDefectiveCar implements Car {
+ @Inject
+ public MultipleConstructorDefectiveCar(@Assisted Color c) throws
FireException {
+ throw new FireException();
+ }
+ }
+
+ public static class WildcardCollection {
+
+ public interface Factory {
+ WildcardCollection create(Collection<?> items);
+ }
+
+ @Inject
+ public WildcardCollection(@Assisted Collection<?> items) { }
+ }
+
+ public void testWildcardGenerics() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(WildcardCollection.Factory.class).toInstance(
+ Factories.create(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;
+
+ @Inject
+ 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).toInstance(
+ Factories.create(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).toInstance(
+ Factories.create(ColoredCarFactory.class, Mustang.class));
+ }
+ });
+ fail();
+ } catch (CreationException expected) {
+ assertContains(expected.getMessage(),
+ "Could not find a suitable constructor in java.lang.Double.",
+ "at " + ColoredCarFactory.class.getName()
+ ".create(FactoriesTest.java");
+ }
+ }
+
+ public void testMethodsDeclaredInObject() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override protected void configure() {
+ bind(Double.class).toInstance(5.0d);
+ bind(ColoredCarFactory.class).toInstance(
+ Factories.create(ColoredCarFactory.class, Mustang.class));
+ }
+ });
+
+ ColoredCarFactory carFactory =
injector.getInstance(ColoredCarFactory.class);
+
+ assertEqualsBothWays(carFactory, carFactory);
+ assertEquals("Factory", carFactory.toString());
+ }
+
+ static class Subaru implements Car {
+ @Inject @Assisted Provider<Color> colorProvider;
+ }
+
+ public void testInjectingProviderOfParameter() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override protected void configure() {
+ bind(ColoredCarFactory.class).toInstance(
+ Factories.create(ColoredCarFactory.class, Subaru.class));
+ }
+ });
+
+ ColoredCarFactory carFactory =
injector.getInstance(ColoredCarFactory.class);
+ Subaru subaru = (Subaru) carFactory.create(Color.RED);
+
+ assertSame(Color.RED, subaru.colorProvider.get());
+ assertSame(Color.RED, subaru.colorProvider.get());
+ }
+
+ public void testInjectingNullParameter() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override protected void configure() {
+ bind(ColoredCarFactory.class).toInstance(
+ Factories.create(ColoredCarFactory.class, Subaru.class));
+ }
+ });
+
+ ColoredCarFactory carFactory =
injector.getInstance(ColoredCarFactory.class);
+ Subaru subaru = (Subaru) carFactory.create(null);
+
+ assertNull(subaru.colorProvider.get());
+ assertNull(subaru.colorProvider.get());
+ }
+
+ public void testFactoryUseBeforeInitialization() {
+ ColoredCarFactory carFactory =
Factories.create(ColoredCarFactory.class, Subaru.class);
+ try {
+ carFactory.create(Color.RED);
+ fail();
+ } catch (IllegalStateException expected) {
+ assertContains(expected.getMessage(),
+ "Factories.create() factories cannot be used until they're
initialized by Guice.");
+ }
+ }
+
+ interface MustangFactory {
+ Mustang create(Color color);
+ }
+
+ public void testFactoryBuildingConcreteTypes() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(double.class).toInstance(5.0d);
+ // note there is no 'thatMakes()' call here:
+ bind(MustangFactory.class).toInstance(
+ Factories.create(MustangFactory.class, Mustang.class));
+ }
+ });
+ MustangFactory factory = injector.getInstance(MustangFactory.class);
+
+ Mustang mustang = factory.create(Color.RED);
+ assertSame(Color.RED, mustang.color);
+ assertEquals(5.0d, mustang.engineSize);
+ }
+
+ static class Fleet {
+ @Inject Mustang mustang;
+ @Inject Camaro camaro;
+ }
+
+ interface FleetFactory {
+ Fleet createFleet(Color color);
+ }
+
+ public void testInjectDeepIntoConstructedObjects() {
+ 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);
+ FleetFactory fleetFactory = Factories.create(FleetFactory.class,
Fleet.class);
+ bind(FleetFactory.class).toInstance(fleetFactory);
+ }
+ });
+
+ FleetFactory fleetFactory = injector.getInstance(FleetFactory.class);
+ Fleet fleet = fleetFactory.createFleet(Color.RED);
+
+ assertSame(Color.RED, fleet.mustang.color);
+ assertEquals(5.0d, fleet.mustang.engineSize);
+ assertSame(Color.RED, fleet.camaro.color);
+ assertEquals(250, fleet.camaro.horsePower);
+ assertEquals(1984, fleet.camaro.modelYear);
+ }
+
+ interface TwoToneCarFactory {
+ Car create(@Assisted("paint") Color paint, @Assisted("fabric") Color
fabric);
+ }
+
+ static class Maxima implements Car {
+ @Inject @Assisted("paint") Color paint;
+ @Inject @Assisted("fabric") Color fabric;
+ }
+
+ public void testDistinctKeys() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(TwoToneCarFactory.class).toInstance(
+ Factories.create(TwoToneCarFactory.class, Maxima.class));
+ }
+ });
+
+ TwoToneCarFactory factory =
injector.getInstance(TwoToneCarFactory.class);
+ Maxima maxima = (Maxima) factory.create(Color.BLACK, Color.GRAY);
+ assertSame(Color.BLACK, maxima.paint);
+ assertSame(Color.GRAY, maxima.fabric);
+ }
+
+ public void testMethodInterceptorsOnAssistedTypes() {
+ final AtomicInteger invocationCount = new AtomicInteger();
+ final MethodInterceptor interceptor = new MethodInterceptor() {
+ public Object invoke(MethodInvocation methodInvocation) throws
Throwable {
+ invocationCount.incrementAndGet();
+ return methodInvocation.proceed();
+ }
+ };
+
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bindInterceptor(Matchers.any(), Matchers.any(), interceptor);
+ bind(Double.class).toInstance(5.0d);
+ bind(ColoredCarFactory.class).toInstance(
+ Factories.create(ColoredCarFactory.class, Mustang.class));
+ }
+ });
+
+ ColoredCarFactory factory =
injector.getInstance(ColoredCarFactory.class);
+ Mustang mustang = (Mustang) factory.create(Color.GREEN);
+ assertEquals(0, invocationCount.get());
+ mustang.drive();
+ assertEquals(1, invocationCount.get());
+ }
+
+ public void testDefaultAssistedAnnotation() throws NoSuchFieldException {
+ assertEqualsBothWays(Factories.DEFAULT_ASSISTED,
+
Subaru.class.getDeclaredField("colorProvider").getAnnotation(Assisted.class));
+ }
+}
Modified: trunk/src/com/google/inject/internal/Annotations.java
==============================================================================
--- trunk/src/com/google/inject/internal/Annotations.java (original)
+++ trunk/src/com/google/inject/internal/Annotations.java Mon Dec 1
22:15:57 2008
@@ -71,7 +71,8 @@
* Adds an error if there is a misplaced annotations on [EMAIL PROTECTED]
type}.
Scoping
* annotations are not allowed on abstract classes or interfaces.
*/
- public static void checkForMisplacedScopeAnnotations(Class<?> type,
Object source, Errors errors) {
+ public static void checkForMisplacedScopeAnnotations(
+ Class<?> type, Object source, Errors errors) {
if (Classes.isConcrete(type)) {
return;
}
Modified: trunk/src/com/google/inject/internal/Errors.java
==============================================================================
--- trunk/src/com/google/inject/internal/Errors.java (original)
+++ trunk/src/com/google/inject/internal/Errors.java Mon Dec 1 22:15:57
2008
@@ -379,7 +379,7 @@
return !errors.isEmpty();
}
- private Errors addMessage(String messageFormat, Object... arguments) {
+ public Errors addMessage(String messageFormat, Object... arguments) {
return addMessage(null, messageFormat, arguments);
}
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"google-guice-dev" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/google-guice-dev?hl=en
-~----------~----~----~----~------~----~------~--~---