/*
* Grapht, an open source dependency injector.
* Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
* Copyright 2010-2014 Regents of the University of Minnesota
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.grouplens.grapht.reflect.internal;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import org.grouplens.grapht.*;
import org.grouplens.grapht.BindingFunctionBuilder.RuleSet;
import org.grouplens.grapht.graph.DAGEdge;
import org.grouplens.grapht.graph.DAGNode;
import org.grouplens.grapht.reflect.Desire;
import org.grouplens.grapht.reflect.Desires;
import org.grouplens.grapht.reflect.InjectionPoint;
import org.grouplens.grapht.reflect.internal.types.*;
import org.grouplens.grapht.solver.DefaultDesireBindingFunction;
import org.grouplens.grapht.solver.DefaultInjector;
import org.junit.Assert;
import org.junit.Test;
import javax.inject.Provider;
import java.util.Map;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertThat;
public class ReflectionInjectionTest {
@Test
public void testProviderCycleInjection() throws Exception {
InjectorBuilder b = InjectorBuilder.create().setProviderInjectionEnabled(true);
Injector i = b.build();
i.getInstance(CycleA.class);
DAGNode<Component,Dependency> root = ((DefaultInjector) i).getSolver().getGraph();
assertThat(root.getReachableNodes(), hasSize(3 + 1));
assertThat(root.getOutgoingEdges(), hasSize(1));
DAGNode<Component, Dependency> anode = root.getOutgoingEdges().iterator().next().getTail();
Assert.assertEquals(CycleA.class, anode.getLabel().getSatisfaction().getErasedType());
Assert.assertEquals(1, anode.getOutgoingEdges().size());
DAGNode<Component, Dependency> bnode = anode.getOutgoingEdges().iterator().next().getTail();
Assert.assertEquals(CycleB.class, bnode.getLabel().getSatisfaction().getErasedType());
Assert.assertEquals(1, bnode.getOutgoingEdges().size());
DAGNode<Component, Dependency> pnode = bnode.getOutgoingEdges().iterator().next().getTail();
Assert.assertEquals(Provider.class, pnode.getLabel().getSatisfaction().getErasedType());
// no outgoing edges...
Assert.assertEquals(0, pnode.getOutgoingEdges().size());
// but a back edge
SetMultimap<DAGNode<Component,Dependency>,DAGEdge<Component, Dependency>> backEdges = ((DefaultInjector) i).getSolver().getBackEdges();
assertThat(backEdges.entries(), hasSize(1));
DAGEdge<Component, Dependency> edge = backEdges.values().iterator().next();
Assert.assertSame(anode, edge.getTail());
}
@Test
public void testTypeCInjectionWithDefaults() throws Exception {
// Test that TypeC can be resolved successfully without any bind rules.
// All of TypeC's dependencies have defaults or are satisfiable.
Desire rootDesire = Desires.create(null, TypeC.class, false);
DefaultInjector r = new DefaultInjector(DefaultDesireBindingFunction.create());
TypeC instance = r.getInstance(TypeC.class);
Assert.assertEquals(5, instance.getIntValue());
Assert.assertNotNull(instance.getInterfaceA());
Assert.assertTrue(instance.getInterfaceA() instanceof TypeB); // ProviderA actually creates TypeB's
Assert.assertSame(instance.getInterfaceA(), instance.getTypeA());
Assert.assertNotNull(instance.getInterfaceB());
Assert.assertTrue(instance.getInterfaceB() instanceof TypeB);
Assert.assertSame(instance.getInterfaceB(), instance.getTypeB());
// also verify memoization
Assert.assertSame(instance, r.getInstance(TypeC.class));
DAGNode<Component, Dependency> resolvedRoot =
r.getSolver().getGraph().getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(rootDesire)).getTail();
assertThat(resolvedRoot.getOutgoingEdges(),
hasSize(5));
Map<InjectionPoint, DAGNode<Component, Dependency>> deps = Maps.newHashMap();
for (DAGEdge<Component, Dependency> e: resolvedRoot.getOutgoingEdges()) {
ReflectionDesire d = (ReflectionDesire) e.getLabel().getInitialDesire();
if (d.getInjectionPoint().equals(TypeC.CONSTRUCTOR)) {
// CycleA ParameterA defaults to 5
Assert.assertFalse(deps.containsKey(TypeC.CONSTRUCTOR));
Assert.assertTrue(e.getTail().getLabel().getSatisfaction() instanceof InstanceSatisfaction);
Assert.assertEquals(5, ((InstanceSatisfaction) e.getTail().getLabel().getSatisfaction()).getInstance());
deps.put(TypeC.CONSTRUCTOR, e.getTail());
} else if (d.getInjectionPoint().equals(TypeC.INTERFACE_A)) {
// An InterfaceA is implemented by TypeA, which is then provided by Provider CycleA
Assert.assertFalse(deps.containsKey(TypeC.INTERFACE_A));
Assert.assertTrue(e.getTail().getLabel().getSatisfaction() instanceof ProviderClassSatisfaction);
Assert.assertEquals(ProviderA.class, ((ProviderClassSatisfaction) e.getTail().getLabel().getSatisfaction()).getProviderType());
deps.put(TypeC.INTERFACE_A, e.getTail());
} else if (d.getInjectionPoint().equals(TypeC.TYPE_A)) {
// CycleA TypeA is provided by a ProviderA
Assert.assertFalse(deps.containsKey(TypeC.TYPE_A));
Assert.assertTrue(e.getTail().getLabel().getSatisfaction() instanceof ProviderClassSatisfaction);
Assert.assertEquals(ProviderA.class, ((ProviderClassSatisfaction) e.getTail().getLabel().getSatisfaction()).getProviderType());
deps.put(TypeC.TYPE_A, e.getTail());
} else if (d.getInjectionPoint().equals(TypeC.INTERFACE_B)) {
// RoleE inherits RoleD and that defaults to TypeB
Assert.assertFalse(deps.containsKey(TypeC.INTERFACE_B));
Assert.assertTrue(e.getTail().getLabel().getSatisfaction() instanceof ClassSatisfaction);
Assert.assertEquals(TypeB.class, e.getTail().getLabel().getSatisfaction().getErasedType());
deps.put(TypeC.INTERFACE_B, e.getTail());
} else if (d.getInjectionPoint().equals(TypeC.TYPE_B)) {
// TypeB is satisfiable on its own
Assert.assertFalse(deps.containsKey(TypeC.TYPE_B));
Assert.assertTrue(e.getTail().getLabel().getSatisfaction() instanceof ClassSatisfaction);
Assert.assertEquals(TypeB.class, e.getTail().getLabel().getSatisfaction().getErasedType());
deps.put(TypeC.TYPE_B, e.getTail());
} else {
Assert.fail();
}
}
// verify that all injection points were tested
Assert.assertTrue(deps.containsKey(TypeC.CONSTRUCTOR));
Assert.assertTrue(deps.containsKey(TypeC.TYPE_A));
Assert.assertTrue(deps.containsKey(TypeC.INTERFACE_A));
Assert.assertTrue(deps.containsKey(TypeC.TYPE_B));
Assert.assertTrue(deps.containsKey(TypeC.INTERFACE_B));
// make sure that nodes are shared where appropriate
Assert.assertSame(deps.get(TypeC.INTERFACE_A), deps.get(TypeC.TYPE_A));
Assert.assertSame(deps.get(TypeC.INTERFACE_B), deps.get(TypeC.TYPE_B));
}
@Test
public void testTypeCInjectionWithBindings() throws Exception {
// Test that TypeC can be injected correctly using bind rules, although
// the bind rule configuration does not need to be very complicated, since
// the resolver and bind rules are already tested.
Desire rootDesire = Desires.create(null, TypeC.class, false);
TypeA a = new TypeA();
TypeB b = new TypeB();
BindingFunctionBuilder bindRules = new BindingFunctionBuilder(false);
bindRules.getRootContext().bind(Integer.class).withQualifier(ParameterA.class).to(10);
bindRules.getRootContext().bind(InterfaceA.class).withQualifier(RoleA.class).to(PrimeA.class);
bindRules.getRootContext().bind(InterfaceB.class).withQualifier(RoleD.class).to(PrimeB.class);
bindRules.getRootContext().bind(TypeA.class).to(a);
bindRules.getRootContext().bind(TypeB.class).to(b);
DefaultInjector r = new DefaultInjector(bindRules.build(RuleSet.EXPLICIT), DefaultDesireBindingFunction.create());
TypeC instance = r.getInstance(TypeC.class);
Assert.assertEquals(10, instance.getIntValue());
Assert.assertNotNull(instance.getInterfaceA());
Assert.assertTrue(instance.getInterfaceA() instanceof PrimeA);
Assert.assertSame(a, instance.getTypeA());
Assert.assertNotNull(instance.getInterfaceB());
Assert.assertTrue(instance.getInterfaceB() instanceof PrimeB);
Assert.assertSame(b, instance.getTypeB());
// also verify memoization
Assert.assertSame(instance, r.getInstance(TypeC.class));
DAGNode<Component, Dependency> resolvedRoot =
r.getSolver().getGraph().getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(rootDesire)).getTail();
assertThat(resolvedRoot.getOutgoingEdges(),
hasSize(5));
Map<InjectionPoint, DAGNode<Component, Dependency>> deps = Maps.newHashMap();
for (DAGEdge<Component, Dependency> e: resolvedRoot.getOutgoingEdges()) {
ReflectionDesire d = (ReflectionDesire) e.getLabel().getInitialDesire();
if (d.getInjectionPoint().equals(TypeC.CONSTRUCTOR)) {
// ParameterA was set to 10
Assert.assertFalse(deps.containsKey(TypeC.CONSTRUCTOR));
Assert.assertTrue(e.getTail().getLabel().getSatisfaction() instanceof InstanceSatisfaction);
Assert.assertEquals(10, ((InstanceSatisfaction) e.getTail().getLabel().getSatisfaction()).getInstance());
deps.put(TypeC.CONSTRUCTOR, e.getTail());
} else if (d.getInjectionPoint().equals(TypeC.INTERFACE_A)) {
// An InterfaceA has been bound to PrimeA
Assert.assertFalse(deps.containsKey(TypeC.INTERFACE_A));
Assert.assertTrue(e.getTail().getLabel().getSatisfaction() instanceof ClassSatisfaction);
Assert.assertEquals(PrimeA.class, e.getTail().getLabel().getSatisfaction().getErasedType());
deps.put(TypeC.INTERFACE_A, e.getTail());
} else if (d.getInjectionPoint().equals(TypeC.TYPE_A)) {
// CycleA TypeA has been bound to an instance
Assert.assertFalse(deps.containsKey(TypeC.TYPE_A));
Assert.assertTrue(e.getTail().getLabel().getSatisfaction() instanceof InstanceSatisfaction);
Assert.assertSame(a, ((InstanceSatisfaction) e.getTail().getLabel().getSatisfaction()).getInstance());
deps.put(TypeC.TYPE_A, e.getTail());
} else if (d.getInjectionPoint().equals(TypeC.INTERFACE_B)) {
// RoleE has been bound to PrimeB
Assert.assertFalse(deps.containsKey(TypeC.INTERFACE_B));
Assert.assertTrue(e.getTail().getLabel().getSatisfaction() instanceof ClassSatisfaction);
Assert.assertEquals(PrimeB.class, e.getTail().getLabel().getSatisfaction().getErasedType());
deps.put(TypeC.INTERFACE_B, e.getTail());
} else if (d.getInjectionPoint().equals(TypeC.TYPE_B)) {
// TypeB has been bound to an instance
Assert.assertFalse(deps.containsKey(TypeC.TYPE_B));
Assert.assertTrue(e.getTail().getLabel().getSatisfaction() instanceof InstanceSatisfaction);
Assert.assertSame(b, ((InstanceSatisfaction) e.getTail().getLabel().getSatisfaction()).getInstance());
deps.put(TypeC.TYPE_B, e.getTail());
} else {
Assert.fail();
}
}
// verify that all injection points were tested
Assert.assertTrue(deps.containsKey(TypeC.CONSTRUCTOR));
Assert.assertTrue(deps.containsKey(TypeC.TYPE_A));
Assert.assertTrue(deps.containsKey(TypeC.INTERFACE_A));
Assert.assertTrue(deps.containsKey(TypeC.TYPE_B));
Assert.assertTrue(deps.containsKey(TypeC.INTERFACE_B));
}
public static class PrimeA implements InterfaceA {
}
public static class PrimeB implements InterfaceB {
}
}