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++;
+ }
+ }
+}