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
-~----------~----~----~----~------~----~------~--~---

Reply via email to