patch from ramakrishna (with very minor changes from me) for an extension SPI for assisted inject.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@1293 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/extensions/assistedinject/src/com/google/inject/assistedinject/AssistedInjectBinding.java b/extensions/assistedinject/src/com/google/inject/assistedinject/AssistedInjectBinding.java
new file mode 100644
index 0000000..5b8d3b8
--- /dev/null
+++ b/extensions/assistedinject/src/com/google/inject/assistedinject/AssistedInjectBinding.java
@@ -0,0 +1,37 @@
+/**

+ * Copyright (C) 2010 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.Key;

+

+import java.util.Collection;

+

+/**

+ * A binding for a factory created by FactoryModuleBuilder.

+ * 

+ * @param <T> The fully qualified type of the factory.

+ *  

+ * @author ramakrishna@google.com (Ramakrishna Rajanna)

+ */

+public interface AssistedInjectBinding<T> {

+  

+  /** Returns the {@link Key} for the factory binding. */

+  Key<T> getKey();

+

+  /** Returns an {@link AssistedMethod} for each method in the factory. */

+  Collection<AssistedMethod> getAssistedMethods();

+}

diff --git a/extensions/assistedinject/src/com/google/inject/assistedinject/AssistedInjectTargetVisitor.java b/extensions/assistedinject/src/com/google/inject/assistedinject/AssistedInjectTargetVisitor.java
new file mode 100644
index 0000000..bc37191
--- /dev/null
+++ b/extensions/assistedinject/src/com/google/inject/assistedinject/AssistedInjectTargetVisitor.java
@@ -0,0 +1,35 @@
+/**

+ * Copyright (C) 2010 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.spi.BindingTargetVisitor;

+

+/**

+ * A visitor for the AssistedInject extension.

+ * <p>

+ * If your {@link BindingTargetVisitor} implements this interface, bindings created by using

+ * {@link FactoryModuleBuilder} will be visited through this interface.

+ *

+ * @author ramakrishna@google.com (Ramakrishna Rajanna)

+ */

+public interface AssistedInjectTargetVisitor<T, V> extends BindingTargetVisitor<T, V> {

+  

+  /**

+   * Visits an {@link AssistedInjectBinding} created through {@link FactoryModuleBuilder}.

+   */

+  V visit(AssistedInjectBinding<? extends T> assistedInjectBinding);

+}

diff --git a/extensions/assistedinject/src/com/google/inject/assistedinject/AssistedMethod.java b/extensions/assistedinject/src/com/google/inject/assistedinject/AssistedMethod.java
new file mode 100644
index 0000000..62be334
--- /dev/null
+++ b/extensions/assistedinject/src/com/google/inject/assistedinject/AssistedMethod.java
@@ -0,0 +1,55 @@
+/**

+ * Copyright (C) 2010 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.TypeLiteral;

+import com.google.inject.spi.Dependency;

+

+import java.lang.reflect.Constructor;

+import java.lang.reflect.Method;

+import java.util.Set;

+

+/**

+ * Details about how a method in an assisted inject factory will be assisted.

+ * 

+ * @author ramakrishna@google.com (Ramakrishna Rajanna)

+ */

+public interface AssistedMethod {

+  

+  /**

+   * Returns the factory method that is being assisted.

+   */

+  Method getFactoryMethod();

+  

+  /**

+   * Returns the implementation type that will be created when the method is

+   * used.

+   */

+  TypeLiteral<?> getImplementationType();

+

+  /**

+   * Returns the constructor that will be used to construct instances of the 

+   * implementation.

+   */

+  Constructor<?> getImplementationConstructor();

+  

+  /**

+   * Returns all non-assisted dependencies required to construct and inject

+   * the implementation.

+   */

+  Set<Dependency<?>> getDependencies();

+}

diff --git a/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryModuleBuilder.java b/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryModuleBuilder.java
index 713068b..7c4765f 100644
--- a/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryModuleBuilder.java
+++ b/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryModuleBuilder.java
@@ -310,7 +310,7 @@
   public <F> Module build(final Key<F> factoryInterface) {
     return new AbstractModule() {
       @Override protected void configure() {
-        Provider<F> provider = new FactoryProvider2<F>(factoryInterface.getTypeLiteral(), bindings);  
+        Provider<F> provider = new FactoryProvider2<F>(factoryInterface, bindings);  
         bind(factoryInterface).toProvider(provider);
       }
     };
diff --git a/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider.java b/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider.java
index 8b336ee..1d9e3f3 100755
--- a/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider.java
+++ b/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider.java
@@ -192,7 +192,7 @@
         }
       }
 
-      return new FactoryProvider2<F>(factoryType, collector);
+      return new FactoryProvider2<F>(Key.get(factoryType), collector);
     }
   }
 
diff --git a/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java b/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java
index f225343..dbccd52 100644
--- a/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java
+++ b/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java
@@ -16,6 +16,9 @@
 
 package com.google.inject.assistedinject;
 
+import static com.google.inject.internal.util.Iterables.getOnlyElement;
+import static com.google.inject.internal.util.Preconditions.checkState;
+
 import com.google.inject.AbstractModule;
 import com.google.inject.Binder;
 import com.google.inject.Binding;
@@ -27,7 +30,7 @@
 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.Annotations;
 import com.google.inject.internal.BytecodeGen;
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
@@ -36,18 +39,18 @@
 import com.google.inject.internal.util.ImmutableMap;
 import com.google.inject.internal.util.ImmutableSet;
 import com.google.inject.internal.util.Iterables;
-import com.google.inject.internal.util.ToStringBuilder;
-
-import static com.google.inject.internal.util.Iterables.getOnlyElement;
 import com.google.inject.internal.util.Lists;
-import static com.google.inject.internal.util.Preconditions.checkState;
-
+import com.google.inject.internal.util.ToStringBuilder;
+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;
@@ -71,7 +74,8 @@
  * @author schmitt@google.com (Peter Schmitt)
  * @author sameb@google.com (Sam Berlin)
  */
-final class FactoryProvider2<F> implements InvocationHandler, Provider<F>, HasDependencies {
+final class FactoryProvider2 <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() {
@@ -96,36 +100,42 @@
       return "@" + Assisted.class.getName() + "(value=)";
     }
   };
-  
+
   /** All the data necessary to perform an assisted inject. */
-  private static class AssistData {
+  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, boolean optimized,
-        List<ThreadLocalProvider> providers,
-        Set<Dependency<?>> dependencies) {
+
+    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;
-      this.dependencies = dependencies;
     }
 
     @Override
@@ -134,33 +144,53 @@
         .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)
-        .add("dependencies", dependencies)
         .toString();
-      
+    }
+
+    public Set<Dependency<?>> getDependencies() {
+      return dependencies;
+    }
+
+    public Method getFactoryMethod() {
+      return factoryMethod;
+    }
+
+    public Constructor<?> getImplementationConstructor() {
+      return constructor;
+    }
+
+    public TypeLiteral<?> getImplementationType() {
+      return implementationType;
     }
   }
 
-  /** the produced type, or null if all methods return concrete types */
-  private final BindingCollector collector;
-  private final ImmutableMap<Method, AssistData> assistDataByMethod;  
+  /** 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.
    */
-  FactoryProvider2(TypeLiteral<F> factoryType, BindingCollector collector) {
-    this.collector = collector;
-
+  FactoryProvider2(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>
@@ -173,7 +203,7 @@
         TypeLiteral<?> returnTypeLiteral = factoryType.getReturnType(method);
         Key<?> returnType;
         try {
-          returnType = getKey(returnTypeLiteral, method, method.getAnnotations(), errors);
+          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.
@@ -188,7 +218,7 @@
         int p = 0;
         List<Key<?>> keys = Lists.newArrayList();
         for (TypeLiteral<?> param : params) {
-          Key<?> paramKey = getKey(param, method, paramAnnotations[p++], errors);
+          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)) {
@@ -199,7 +229,7 @@
           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) {
@@ -207,13 +237,13 @@
         }
         InjectionPoint ctorInjectionPoint;
         try {
-          ctorInjectionPoint = 
+          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);
@@ -232,8 +262,8 @@
           optimized = true;
         }
         assistDataBuilder.put(method,
-            new AssistData(constructor, returnType, immutableParamList,
-                optimized, providers, removeAssistedDeps(deps)));
+            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.
@@ -245,7 +275,7 @@
     } catch (ErrorsException e) {
       throw new ConfigurationException(e.getErrors().getMessages());
     }
-    
+
     factory = factoryRawType.cast(Proxy.newProxyInstance(BytecodeGen.getClassLoader(factoryRawType),
         new Class[] { factoryRawType }, this));
   }
@@ -253,7 +283,7 @@
   public F get() {
     return factory;
   }
-  
+
   public Set<Dependency<?>> getDependencies() {
     Set<Dependency<?>> combinedDeps = new HashSet<Dependency<?>>();
     for(AssistData data : assistDataByMethod.values()) {
@@ -261,6 +291,25 @@
     }
     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 <V, T> 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);
+  }
 
   /**
    * Returns true if the ConfigurationException is due to an error of TypeLiteral not being fully
@@ -293,7 +342,7 @@
     } else {
       errors = errors.withSource(returnType).withSource(implementation);
     }
-    
+
     Class<?> rawType = implementation.getRawType();
     if (Modifier.isInterface(rawType.getModifiers())) {
       errors.addMessage(
@@ -309,7 +358,7 @@
       errors.cannotInjectInnerClass(rawType);
       throw errors.toException();
     }
-    
+
     Constructor<?> matchingConstructor = null;
     boolean anyAssistedInjectConstructors = false;
     // Look for AssistedInject constructors...
@@ -330,7 +379,7 @@
         }
       }
     }
-    
+
     if(!anyAssistedInjectConstructors) {
       // If none existed, use @Inject.
       try {
@@ -371,7 +420,7 @@
     int p = 0;
     List<Key<?>> constructorKeys = Lists.newArrayList();
     for (TypeLiteral<?> param : params) {
-      Key<?> paramKey = getKey(param, constructor, paramAnnotations[p++],
+      Key<?> paramKey = Annotations.getKey(param, constructor, paramAnnotations[p++],
           errors);
       constructorKeys.add(paramKey);
     }
@@ -403,7 +452,7 @@
     }
     return builder.build();
   }
-  
+
   /** Return all non-assisted dependencies. */
   private Set<Dependency<?>> removeAssistedDeps(Set<Dependency<?>> deps) {
     ImmutableSet.Builder<Dependency<?>> builder = ImmutableSet.builder();
@@ -415,7 +464,7 @@
     }
     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
@@ -430,7 +479,7 @@
     }
     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}.
@@ -521,18 +570,14 @@
             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) {
-          TypeLiteral implementation = collector.getBindings().get(returnType);
-          if (implementation != null) {
-            binder.bind(assistedReturnType).toConstructor(constructor, implementation);
-          } else {
-            binder.bind(assistedReturnType).toConstructor(constructor, (TypeLiteral) returnType.getTypeLiteral());
-          }
+          binder.bind(assistedReturnType).toConstructor(
+              constructor, (TypeLiteral)data.implementationType);
         }
       }
     };
@@ -541,7 +586,7 @@
     Binding binding = forCreate.getBinding(assistedReturnType);
     // If we have providers cached in data, cache the binding for future optimizations.
     if(data.optimized) {
-      data.cachedBinding = binding; 
+      data.cachedBinding = binding;
     }
     return binding;
   }
@@ -607,9 +652,9 @@
 
     return false;
   }
-  
+
   // not <T> because we'll never know and this is easier than suppressing warnings.
-  private static class ThreadLocalProvider extends ThreadLocal<Object> implements Provider<Object> {    
+  private static class ThreadLocalProvider extends ThreadLocal<Object> implements Provider<Object> {
     @Override
     protected Object initialValue() {
       throw new IllegalStateException(
diff --git a/extensions/assistedinject/test/com/google/inject/assistedinject/ExtensionSpiTest.java b/extensions/assistedinject/test/com/google/inject/assistedinject/ExtensionSpiTest.java
new file mode 100644
index 0000000..620741f
--- /dev/null
+++ b/extensions/assistedinject/test/com/google/inject/assistedinject/ExtensionSpiTest.java
@@ -0,0 +1,221 @@
+/**

+ * Copyright (C) 2010 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.name.Names.named;

+

+import com.google.inject.AbstractModule;

+import com.google.inject.Binding;

+import com.google.inject.Guice;

+import com.google.inject.Inject;

+import com.google.inject.Injector;

+import com.google.inject.Key;

+import com.google.inject.Stage;

+import com.google.inject.internal.util.ImmutableList;

+import com.google.inject.internal.util.ImmutableSet;

+import com.google.inject.internal.util.Iterables;

+import com.google.inject.internal.util.Lists;

+import com.google.inject.name.Named;

+import com.google.inject.spi.DefaultBindingTargetVisitor;

+import com.google.inject.spi.Dependency;

+import com.google.inject.spi.Element;

+import com.google.inject.spi.Elements;

+

+import junit.framework.AssertionFailedError;

+import junit.framework.TestCase;

+

+import java.util.List;

+import java.util.Set;

+import java.util.logging.Logger;

+

+/**

+ * Tests for AssistedInject Spi.

+ *

+ * @author ramakrishna@google.com (Ramakrishna Rajanna)

+ */

+public class ExtensionSpiTest extends TestCase {

+

+  public final void testSpiOnElements() throws Exception {

+    AssistedInjectSpiVisitor visitor = new AssistedInjectSpiVisitor();

+    Integer count = 0;

+    for(Element element : Elements.getElements(new Module())) {

+      if(element instanceof Binding) {

+        assertEquals(count++, ((Binding<?>)element).acceptTargetVisitor(visitor));

+      }

+    }

+    validateVisitor(visitor);

+  }

+

+  public void testSpiOnVisitor() throws Exception {

+    AssistedInjectSpiVisitor visitor = new AssistedInjectSpiVisitor();

+    Integer count = 0;

+    Injector injector = Guice.createInjector(new Module());

+    for(Binding<?> binding : injector.getBindings().values()) {

+      assertEquals(count++, binding.acceptTargetVisitor(visitor));

+    }

+    validateVisitor(visitor);

+  }

+

+  private void validateVisitor(AssistedInjectSpiVisitor visitor) throws Exception {

+    assertEquals(1, visitor.assistedBindingCount);

+    List<AssistedMethod> assistedMethods =

+        Lists.newArrayList(Iterables.getOnlyElement(

+            visitor.assistedInjectBindings).getAssistedMethods());

+    assertEquals(7, assistedMethods.size());

+    assertEquals(1, visitor.assistedBindingCount);

+    assertEquals(1, visitor.assistedInjectBindings.size());

+

+    // Validate for each of the methods in AnimalFactory

+    validateCreateAStrangeCatAsAnimal(assistedMethods.get(0));

+    validatecreateStrangeCatWithConstructorForOwner(assistedMethods.get(1));

+    validatecreateStrangeCatWithConstructorForAge(assistedMethods.get(2));

+    validateCreateCatWithANonAssistedDependency(assistedMethods.get(3));

+    validateCreateCat(assistedMethods.get(4));

+    validateCreateASimpleCatAsAnimal(assistedMethods.get(5));

+    validateCreateCatWithNonAssistedDependencies(assistedMethods.get(6));

+

+  }

+

+  private void validateCreateAStrangeCatAsAnimal(AssistedMethod assistedMethod) {

+    validateAssistedMethod(assistedMethod, "createAStrangeCatAsAnimal",

+      StrangeCat.class, ImmutableList.<Key<?>>of());

+  }

+

+  private void validatecreateStrangeCatWithConstructorForOwner(AssistedMethod assistedMethod) {

+    validateAssistedMethod(assistedMethod, "createStrangeCatWithConstructorForOwner",

+      StrangeCat.class, ImmutableList.<Key<?>>of());

+  }

+

+  private void validatecreateStrangeCatWithConstructorForAge(AssistedMethod assistedMethod) {

+    validateAssistedMethod(assistedMethod, "createStrangeCatWithConstructorForAge",

+      StrangeCat.class, ImmutableList.<Key<?>>of());

+  }

+

+  private void validateCreateCatWithANonAssistedDependency(AssistedMethod assistedMethod)

+      throws Exception {

+    validateAssistedMethod(assistedMethod, "createCatWithANonAssistedDependency",

+        CatWithAName.class, ImmutableList.<Key<?>>of(Key.get(String.class, named("catName2"))));

+  }

+

+  private void validateCreateCat(AssistedMethod assistedMethod) throws Exception {

+    validateAssistedMethod(assistedMethod, "createCat", Cat.class, ImmutableList.<Key<?>>of());

+  }

+

+  private void validateCreateASimpleCatAsAnimal(AssistedMethod assistedMethod) {

+    validateAssistedMethod(assistedMethod, "createASimpleCatAsAnimal", SimpleCat.class,

+        ImmutableList.<Key<?>>of());

+  }

+

+  private void validateCreateCatWithNonAssistedDependencies(AssistedMethod assistedMethod) {

+    List<Key<?>> dependencyKeys = ImmutableList.<Key<?>>of(

+        Key.get(String.class, named("catName1")),

+        Key.get(String.class, named("petName")),        

+        Key.get(Integer.class, named("age")));

+    validateAssistedMethod(assistedMethod, "createCatWithNonAssistedDependencies",

+      ExplodingCat.class, dependencyKeys);

+  }

+

+  private void validateAssistedMethod(AssistedMethod assistedMethod, String factoryMethodName,

+                                      Class clazz, List<Key<?>> dependencyKeys){

+    assertEquals(factoryMethodName, assistedMethod.getFactoryMethod().getName());

+    assertEquals(clazz, assistedMethod.getImplementationConstructor().getDeclaringClass());

+    assertEquals(dependencyKeys.size(), assistedMethod.getDependencies().size());

+    for (Dependency<?> dependency : assistedMethod.getDependencies()) {

+      assertTrue(dependencyKeys.contains(dependency.getKey()));

+    }

+    assertEquals(clazz, assistedMethod.getImplementationType().getType());

+  }

+

+

+  interface AnimalFactory {

+    Cat createCat(String owner);

+    CatWithAName createCatWithANonAssistedDependency(String owner);

+    @Named("SimpleCat") Animal createASimpleCatAsAnimal(String owner);

+    Animal createAStrangeCatAsAnimal(String owner);

+    StrangeCat createStrangeCatWithConstructorForOwner(String owner);

+    StrangeCat createStrangeCatWithConstructorForAge(Integer age);

+    ExplodingCat createCatWithNonAssistedDependencies(String owner);

+  }

+

+  interface Animal {}

+

+  private static class Cat implements Animal {

+    @Inject Cat(@Assisted String owner) {}

+  }

+

+  private static class SimpleCat implements Animal {

+    @Inject SimpleCat(@Assisted String owner) {

+    }

+  }

+

+  private static class StrangeCat implements Animal {

+    @AssistedInject StrangeCat(@Assisted String owner) {}

+    @AssistedInject StrangeCat(@Assisted Integer age) {}

+  }

+

+  private static class ExplodingCat implements Animal {

+    @Inject public ExplodingCat(@Named("catName1") String name, @Assisted String owner,

+                                @Named("age") Integer age, @Named("petName") String petName) {}

+  }

+

+  private static class CatWithAName extends Cat {

+    @Inject CatWithAName(@Assisted String owner, @Named("catName2") String name) {

+      super(owner);

+    }

+  }

+

+  public class Module extends AbstractModule{

+    @Override

+    protected void configure() {

+      bind(String.class).annotatedWith(named("catName1")).toInstance("kitty1");

+      bind(String.class).annotatedWith(named("catName2")).toInstance("kitty2");

+      bind(String.class).annotatedWith(named("petName")).toInstance("pussy");

+      bind(Integer.class).annotatedWith(named("age")).toInstance(12);

+      install(new FactoryModuleBuilder()

+        .implement(Animal.class, StrangeCat.class)

+        .implement(Animal.class, named("SimpleCat"), SimpleCat.class)

+        .build(AnimalFactory.class));

+    }

+  }

+

+  public class AssistedInjectSpiVisitor extends DefaultBindingTargetVisitor<Object, Integer>

+      implements AssistedInjectTargetVisitor<Object, Integer>  {

+

+    private final Set<Class> allowedClasses = 

+      ImmutableSet.<Class> of(

+        Injector.class, Stage.class, Logger.class,

+        String.class, Integer.class);

+    

+    private int assistedBindingCount = 0;

+    private int currentCount = 0;

+    private List<AssistedInjectBinding<?>> assistedInjectBindings = Lists.newArrayList();

+

+    public Integer visit(AssistedInjectBinding assistedInjectBinding) {

+      assistedInjectBindings.add(assistedInjectBinding);

+      assistedBindingCount++;

+      return currentCount++;

+    }

+

+    @Override

+    protected Integer visitOther(Binding<? extends Object> binding) {

+      if(!allowedClasses.contains(binding.getKey().getTypeLiteral().getRawType())) {

+        throw new AssertionFailedError("invalid other binding: " + binding);

+      }

+      return currentCount++;

+    }

+  }

+}