/* * 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.solver; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.*; import org.grouplens.grapht.CachePolicy; import org.grouplens.grapht.Component; import org.grouplens.grapht.Dependency; import org.grouplens.grapht.annotation.AnnotationBuilder; import org.grouplens.grapht.context.ContextElements; import org.grouplens.grapht.context.ContextMatcher; import org.grouplens.grapht.context.ContextPattern; import org.grouplens.grapht.graph.DAGEdge; import org.grouplens.grapht.graph.DAGNode; import org.grouplens.grapht.reflect.*; import org.junit.Assert; import org.junit.Test; import javax.inject.Qualifier; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertThat; public class DependencySolverTest { private DependencySolver createSolver(ListMultimap<ContextMatcher, BindRule> rules) { return DependencySolver.newBuilder() .addBindingFunction(new RuleBasedBindingFunction(rules)) .setDefaultPolicy(CachePolicy.NO_PREFERENCE) .setMaxDepth(100) .build(); } // bypass synthetic root and return node that resolves the desire private DAGNode<Component, Dependency> getRoot(DependencySolver r, Desire d) { return r.getGraph().getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(d)).getTail(); } @Test public void testCachePolicySuccess() throws Exception { // Test that satisfactions formed with different cache policies // correctly restrict merging of nodes Satisfaction sa = new MockSatisfaction(A.class, new ArrayList<Desire>()); Desire da = new MockDesire(); Satisfaction sb = new MockSatisfaction(B.class, Arrays.asList(da)); Desire ra = new MockDesire(sa); Desire rb = new MockDesire(sb); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); final Class<?> type = B.class; final MockQualifierMatcher qualifier = MockQualifierMatcher.any(); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type, qualifier)), new MockBindRule(da, ra).setCachePolicy(CachePolicy.MEMOIZE)); bindings.put(ContextPattern.any(), new MockBindRule(da, ra).setCachePolicy(CachePolicy.NEW_INSTANCE)); DependencySolver r = createSolver(bindings.build()); r.resolve(rb); r.resolve(da); // should create a single extra sa node with different cache policy assertThat(r.getGraph().getReachableNodes(), hasSize(3 + 1)); DAGNode<Component, Dependency> bnode = getRoot(r, rb); Assert.assertEquals(CachePolicy.NO_PREFERENCE, bnode.getLabel().getCachePolicy()); DAGNode<Component, Dependency> adepnode = getNode(bnode, sa, da); Assert.assertNotNull(adepnode); Assert.assertEquals(CachePolicy.MEMOIZE, adepnode.getLabel().getCachePolicy()); DAGNode<Component, Dependency> anode = getRoot(r, da); Assert.assertNotSame(adepnode, anode); Assert.assertEquals(CachePolicy.NEW_INSTANCE, anode.getLabel().getCachePolicy()); } @Test public void testNoDependenciesSuccess() throws Exception { // Test resolving a satisfaction that has no dependencies and is already satisfiable Satisfaction sat = new MockSatisfaction(A.class, new ArrayList<Desire>()); Desire desire = new MockDesire(sat); DependencySolver r = createSolver(ArrayListMultimap.<ContextMatcher, BindRule>create()); r.resolve(desire); assertThat(r.getGraph().getReachableNodes(), hasSize(2)); DAGNode<Component, Dependency> node = getRoot(r, desire); Assert.assertEquals(sat, node.getLabel().getSatisfaction()); Assert.assertTrue(node.getOutgoingEdges().isEmpty()); Assert.assertTrue(r.getGraph().getReachableNodes().contains(node)); } @Test public void testSingleDependencySuccess() throws Exception { // Test resolving a satisfaction with a single dependency that is already satisfiable Satisfaction dep = new MockSatisfaction(B.class); Desire depDesire = new MockDesire(dep); Satisfaction rootSat = new MockSatisfaction(A.class, Arrays.asList(depDesire)); Desire rootDesire = new MockDesire(rootSat); DependencySolver r = createSolver(ArrayListMultimap.<ContextMatcher, BindRule>create()); r.resolve(rootDesire); DAGNode<Component, Dependency> rootNode = getRoot(r, rootDesire); Assert.assertEquals(2 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertEquals(rootSat, rootNode.getLabel().getSatisfaction()); Assert.assertEquals(1, rootNode.getOutgoingEdges().size()); Assert.assertEquals(dep, rootNode.getOutgoingEdges().iterator().next().getTail().getLabel().getSatisfaction()); } @Test public void testSingleDependencyChainedDesiresSuccess() throws Exception { // Test resolving a satisfaction with a single dependency through multiple desires/bind rules Satisfaction dep = new MockSatisfaction(B.class); Desire d1 = new MockDesire(); Desire d2 = new MockDesire(); Desire d3 = new MockDesire(dep); Satisfaction root = new MockSatisfaction(A.class, Arrays.asList(d1)); Desire rootDesire = new MockDesire(root); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(d1, d2), new MockBindRule(d2, d3)); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); DAGNode<Component, Dependency> rootNode = getRoot(r, rootDesire); Assert.assertEquals(2 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertEquals(root, rootNode.getLabel().getSatisfaction()); Assert.assertEquals(1, rootNode.getOutgoingEdges().size()); Assert.assertEquals(dep, rootNode.getOutgoingEdges().iterator().next().getTail().getLabel().getSatisfaction()); } @Test public void testMultipleSatisfiableDesiresSuccess() throws Exception { // Test resolving a single satisfaction, where the chain of bind rules // contains multiple satisfiable desires, and the deepest is selected Satisfaction dep = new MockSatisfaction(B.class); Satisfaction s1 = new MockSatisfaction(C.class); Satisfaction s2 = new MockSatisfaction(Ap.class); Desire d1 = new MockDesire(s1); Desire d2 = new MockDesire(s2); Desire d3 = new MockDesire(dep); Satisfaction root = new MockSatisfaction(A.class, Arrays.asList(d1)); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(d1, d2), new MockBindRule(d2, d3)); Desire rootDesire = new MockDesire(root); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); DAGNode<Component, Dependency> rootNode = getRoot(r, rootDesire); Assert.assertEquals(2 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertEquals(root, rootNode.getLabel().getSatisfaction()); Assert.assertEquals(1, rootNode.getOutgoingEdges().size()); Assert.assertEquals(dep, rootNode.getOutgoingEdges().iterator().next().getTail().getLabel().getSatisfaction()); } @Test public void testParentReferencesGrandChildSuccess() throws Exception { // Test that when a desire references a child that child's desire (2 desires total), // the parent desire is resolved properly (note that a solution by walking // up leaf nodes doesn't work in this case since it will encounter the parent // and child at the same time). Desire d1 = new MockDesire(); Desire d2 = new MockDesire(); Satisfaction s3 = new MockSatisfaction(C.class); // leaf/grandchild Satisfaction s2 = new MockSatisfaction(B.class, Arrays.asList(d2)); // child Satisfaction s1 = new MockSatisfaction(A.class, Arrays.asList(d1, d2)); Desire b1 = new MockDesire(s2); Desire b2 = new MockDesire(s3); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(d1, b1), new MockBindRule(d2, b2)); Desire rootDesire = new MockDesire(s1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); DAGNode<Component, Dependency> rootNode = getRoot(r, rootDesire); Assert.assertEquals(3 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertEquals(s1, rootNode.getLabel().getSatisfaction()); DAGNode<Component, Dependency> n1 = getNode(r.getGraph(), Component.create(s1, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n2 = getNode(r.getGraph(), Component.create(s2, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n3 = getNode(r.getGraph(), Component.create(s3, CachePolicy.NO_PREFERENCE)); assertThat(Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))), hasSize(1)); Assert.assertEquals(d1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).iterator().next().getLabel().getInitialDesire()); Assert.assertEquals(1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).size()); Assert.assertEquals(d2, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).iterator().next().getLabel().getInitialDesire()); Assert.assertEquals(1, Sets.filter(n2.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).size()); Assert.assertEquals(d2, Sets.filter(n2.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).iterator().next().getLabel().getInitialDesire()); } @Test public void testContextRoleMatchSuccess() throws Exception { // Test that qualifiers are properly remembered in the context // - note that this is different than having a qualifier-binding, that is // part of the bind rule's match implementation Qual qualifier1 = AnnotationBuilder.of(Qual.class).setValue(0).build(); Qual qualifier2 = AnnotationBuilder.of(Qual.class).setValue(1).build(); Desire dr1 = new MockDesire(null, qualifier1); Desire dr2 = new MockDesire(null, qualifier2); Desire d3 = new MockDesire(); Satisfaction r1 = new MockSatisfaction(A.class, Arrays.asList(dr1, dr2)); Satisfaction r2 = new MockSatisfaction(B.class, Arrays.asList(d3)); Satisfaction r3 = new MockSatisfaction(C.class, Arrays.asList(d3)); Satisfaction r4 = new MockSatisfaction(Cp.class); Satisfaction or4 = new MockSatisfaction(Bp.class); Desire br1 = new MockDesire(r2); Desire br2 = new MockDesire(r3); Desire b3 = new MockDesire(r4); Desire ob3 = new MockDesire(or4); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(dr1, br1), new MockBindRule(dr2, br2)); final Class<?> type = Object.class; final MockQualifierMatcher qualifier = MockQualifierMatcher.match(qualifier1); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type, qualifier)), new MockBindRule(d3, b3)); final Class<?> type1 = Object.class; final MockQualifierMatcher qualifier3 = MockQualifierMatcher.match(qualifier2); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type1, qualifier3)), new MockBindRule(d3, ob3)); Desire rootDesire = new MockDesire(r1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); DAGNode<Component, Dependency> rootNode = getRoot(r, rootDesire); Assert.assertEquals(5 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root DAGNode<Component, Dependency> n1 = getNode(r.getGraph(), Component.create(r1, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n2 = getNode(r.getGraph(), Component.create(r2, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n3 = getNode(r.getGraph(), Component.create(r3, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n4 = getNode(r.getGraph(), Component.create(r4, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> on4 = getNode(r.getGraph(), Component.create(or4, CachePolicy.NO_PREFERENCE)); Assert.assertEquals(n1, rootNode); Assert.assertEquals(2, n1.getOutgoingEdges().size()); Assert.assertEquals(1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).size()); Assert.assertEquals(dr1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).iterator().next().getLabel().getInitialDesire()); Assert.assertEquals(1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).size()); Assert.assertEquals(dr2, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).iterator().next().getLabel().getInitialDesire()); Assert.assertEquals(1, Sets.filter(n2.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n4))).size()); Assert.assertEquals(d3, Sets.filter(n2.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n4))).iterator().next().getLabel().getInitialDesire()); Assert.assertEquals(1, Sets.filter(n3.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(on4))).size()); Assert.assertEquals(d3, Sets.filter(n3.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(on4))).iterator().next().getLabel().getInitialDesire()); } @Test public void testSimpleContextMatchSuccess() throws Exception { // Test that a context-specific bind rule is included and selected Desire d1 = new MockDesire(); Satisfaction r1 = new MockSatisfaction(A.class, Arrays.asList(d1)); Satisfaction r2 = new MockSatisfaction(B.class); Satisfaction or2 = new MockSatisfaction(Bp.class); Desire b1 = new MockDesire(r2); Desire ob1 = new MockDesire(or2); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.put(ContextPattern.any(), new MockBindRule(d1, b1)); final Class<?> type = A.class; final MockQualifierMatcher qualifier = MockQualifierMatcher.any(); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type, qualifier)), new MockBindRule(d1, ob1)); Desire rootDesire = new MockDesire(r1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); DAGNode<Component, Dependency> rootNode = getRoot(r, rootDesire); Assert.assertEquals(2 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertEquals(r1, rootNode.getLabel().getSatisfaction()); Assert.assertEquals(1, rootNode.getOutgoingEdges().size()); Assert.assertEquals(or2, rootNode.getOutgoingEdges().iterator().next().getTail().getLabel().getSatisfaction()); } @Test public void testContextClosenessMatchSuccess() throws Exception { // Test that between two context bind rules, the closest is chosen Desire d1 = new MockDesire(); Desire d2 = new MockDesire(); Satisfaction or3 = new MockSatisfaction(Cp.class); Satisfaction r3 = new MockSatisfaction(C.class); Satisfaction r2 = new MockSatisfaction(B.class, Arrays.asList(d2)); Satisfaction r1 = new MockSatisfaction(A.class, Arrays.asList(d1)); Desire b1 = new MockDesire(r2); Desire b2 = new MockDesire(r3); Desire ob2 = new MockDesire(or3); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.put(ContextPattern.any(), new MockBindRule(d1, b1)); // for this test, CycleA is farther than B so b2 should be selected over ob2 final Class<?> type = A.class; final MockQualifierMatcher qualifier = MockQualifierMatcher.any(); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type, qualifier)), new MockBindRule(d2, ob2)); final Class<?> type1 = B.class; final MockQualifierMatcher qualifier1 = MockQualifierMatcher.any(); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type1, qualifier1)), new MockBindRule(d2, b2)); Desire rootDesire = new MockDesire(r1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); DAGNode<Component, Dependency> n1 = getNode(r.getGraph(), Component.create(r1, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n2 = getNode(r.getGraph(), Component.create(r2, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n3 = getNode(r.getGraph(), Component.create(r3, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> on3 = getNode(r.getGraph(), Component.create(or3, CachePolicy.NO_PREFERENCE)); Assert.assertEquals(3 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertNotNull(n1); Assert.assertNotNull(n2); Assert.assertNotNull(n3); Assert.assertNull(on3); Assert.assertEquals(1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).size()); Assert.assertEquals(d1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).iterator().next().getLabel().getInitialDesire()); Assert.assertEquals(1, Sets.filter(n2.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).size()); Assert.assertEquals(d2, Sets.filter(n2.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).iterator().next().getLabel().getInitialDesire()); } @Test public void testContextLengthMatchSuccess() throws Exception { // Test that between two context bind rules, the longest is chosen // if their closeness is equal Desire d1 = new MockDesire(); Desire d2 = new MockDesire(); Satisfaction or3 = new MockSatisfaction(Cp.class); Satisfaction r3 = new MockSatisfaction(C.class); Satisfaction r2 = new MockSatisfaction(B.class, Arrays.asList(d2)); Satisfaction r1 = new MockSatisfaction(A.class, Arrays.asList(d1)); Desire b1 = new MockDesire(r2); Desire b2 = new MockDesire(r3); Desire ob2 = new MockDesire(or3); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.put(ContextPattern.any(), new MockBindRule(d1, b1)); // for this test, AB is longer than CycleA so b2 is selected over ob2 final Class<?> type = A.class; final MockQualifierMatcher qualifier = MockQualifierMatcher.any(); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type, qualifier)), new MockBindRule(d2, ob2)); final Class<?> type1 = A.class; final MockQualifierMatcher qualifier1 = MockQualifierMatcher.any(); final Class<?> type2 = B.class; final MockQualifierMatcher qualifier2 = MockQualifierMatcher.any(); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type1, qualifier1), ContextElements.matchType(type2, qualifier2) ), new MockBindRule(d2, b2)); Desire rootDesire = new MockDesire(r1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); DAGNode<Component, Dependency> n1 = getNode(r.getGraph(), Component.create(r1, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n2 = getNode(r.getGraph(), Component.create(r2, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n3 = getNode(r.getGraph(), Component.create(r3, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> on3 = getNode(r.getGraph(), Component.create(or3, CachePolicy.NO_PREFERENCE)); Assert.assertEquals(3 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertNotNull(n1); Assert.assertNotNull(n2); Assert.assertNotNull(n3); Assert.assertNull(on3); Assert.assertEquals(1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).size()); Assert.assertEquals(d1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).iterator().next().getLabel().getInitialDesire()); Assert.assertEquals(1, Sets.filter(n2.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).size()); Assert.assertEquals(d2, Sets.filter(n2.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).iterator().next().getLabel().getInitialDesire()); } @Test public void testIgnoreDefaultContextBindingSuccess() throws Exception { // Test that a specific context binding is preferred over a valid default // context binding for the same type. This can be inferred from the above // tests but it is nice to ensure it works as expected Desire d1 = new MockDesire(); Satisfaction or2 = new MockSatisfaction(Bp.class); Satisfaction r2 = new MockSatisfaction(B.class); Satisfaction r1 = new MockSatisfaction(A.class, Arrays.asList(d1)); Desire b1 = new MockDesire(r2); Desire ob1 = new MockDesire(or2); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); // for this test, CycleA is more specific than default, so b2 is selected bindings.put(ContextPattern.any(), new MockBindRule(d1, ob1)); final Class<?> type = A.class; final MockQualifierMatcher qualifier = MockQualifierMatcher.any(); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type, qualifier)), new MockBindRule(d1, b1)); Desire rootDesire = new MockDesire(r1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); DAGNode<Component, Dependency> n1 = getNode(r.getGraph(), Component.create(r1, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n2 = getNode(r.getGraph(), Component.create(r2, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> on2 = getNode(r.getGraph(), Component.create(or2, CachePolicy.NO_PREFERENCE)); Assert.assertEquals(2 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertNotNull(n1); Assert.assertNotNull(n2); Assert.assertNull(on2); Assert.assertEquals(1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).size()); Assert.assertEquals(d1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).iterator().next().getLabel().getInitialDesire()); } @Test public void testMultipleDependenciesSuccess() throws Exception { // Test that a satisfaction with multiple dependencies is correctly resolved Desire d1 = new MockDesire(); Desire d2 = new MockDesire(); Desire d3 = new MockDesire(); Satisfaction r1 = new MockSatisfaction(A.class, Arrays.asList(d1, d2, d3)); Satisfaction sd1 = new MockSatisfaction(B.class); Satisfaction sd2 = new MockSatisfaction(C.class); Satisfaction sd3 = new MockSatisfaction(Cp.class); Desire b1 = new MockDesire(sd1); Desire b2 = new MockDesire(sd2); Desire b3 = new MockDesire(sd3); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(d1, b1), new MockBindRule(d2, b2), new MockBindRule(d3, b3)); Desire rootDesire = new MockDesire(r1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); DAGNode<Component, Dependency> rootNode = getRoot(r, rootDesire); Assert.assertEquals(4 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertEquals(r1, rootNode.getLabel().getSatisfaction()); Assert.assertEquals(3, rootNode.getOutgoingEdges().size()); DAGNode<Component, Dependency> n1 = getNode(r.getGraph(), Component.create(sd1, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n2 = getNode(r.getGraph(), Component.create(sd2, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n3 = getNode(r.getGraph(), Component.create(sd3, CachePolicy.NO_PREFERENCE)); Assert.assertEquals(1, Sets.filter(rootNode.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n1))).size()); Assert.assertEquals(d1, Sets.filter(rootNode.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n1))).iterator().next().getLabel().getInitialDesire()); Assert.assertEquals(1, Sets.filter(rootNode.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).size()); Assert.assertEquals(d2, Sets.filter(rootNode.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).iterator().next().getLabel().getInitialDesire()); Assert.assertEquals(1, Sets.filter(rootNode.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).size()); Assert.assertEquals(d3, Sets.filter(rootNode.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).iterator().next().getLabel().getInitialDesire()); } @Test public void testMultipleRootsSharedDependencySuccess() throws Exception { // Test multiple root desires that resolve to nodes that share // a dependency, and verify that the resolved dependency is the same DAGNode<Component, DesireChain> Desire d1 = new MockDesire(); Desire d2 = new MockDesire(); Desire d3 = new MockDesire(); Satisfaction r1 = new MockSatisfaction(A.class, Arrays.asList(d1, d2, d3)); Satisfaction sd1 = new MockSatisfaction(B.class); Desire b1 = new MockDesire(sd1); Desire b2 = new MockDesire(sd1); Desire b3 = new MockDesire(sd1); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(d1, b1), new MockBindRule(d2, b2), new MockBindRule(d3, b3)); Desire rootDesire = new MockDesire(r1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); DAGNode<Component, Dependency> rootNode = getRoot(r, rootDesire); Assert.assertEquals(2 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertEquals(r1, rootNode.getLabel().getSatisfaction()); Assert.assertEquals(3, rootNode.getOutgoingEdges().size()); Set<Desire> edges = new HashSet<Desire>(); for (DAGEdge<Component, Dependency> e: rootNode.getOutgoingEdges()) { Assert.assertEquals(sd1, e.getTail().getLabel().getSatisfaction()); edges.add(e.getLabel().getInitialDesire()); } Assert.assertTrue(edges.contains(d1)); Assert.assertTrue(edges.contains(d2)); Assert.assertTrue(edges.contains(d3)); } @Test public void testChainedDependenciesSuccess() throws Exception { // Test multiple levels of dependencies Desire d1 = new MockDesire(); Desire d2 = new MockDesire(); Satisfaction s3 = new MockSatisfaction(C.class); Satisfaction s2 = new MockSatisfaction(B.class, Arrays.asList(d2)); Satisfaction s1 = new MockSatisfaction(A.class, Arrays.asList(d1)); Desire b1 = new MockDesire(s2); Desire b2 = new MockDesire(s3); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(d1, b1), new MockBindRule(d2, b2)); Desire rootDesire = new MockDesire(s1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); DAGNode<Component, Dependency> rootNode = getRoot(r, rootDesire); Assert.assertEquals(3 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertEquals(s1, rootNode.getLabel().getSatisfaction()); DAGNode<Component, Dependency> n1 = getNode(r.getGraph(), Component.create(s1, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n2 = getNode(r.getGraph(), Component.create(s2, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n3 = getNode(r.getGraph(), Component.create(s3, CachePolicy.NO_PREFERENCE)); Assert.assertEquals(1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).size()); Assert.assertEquals(d1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).iterator().next().getLabel().getInitialDesire()); Assert.assertEquals(1, Sets.filter(n2.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).size()); Assert.assertEquals(d2, Sets.filter(n2.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n3))).iterator().next().getLabel().getInitialDesire()); } @Test public void testComplexDependenciesSuccess() throws Exception { // Test a contrived example of a reasonably complex dependency scenario // that tests contexts, qualifiers, shared, and split nodes Qual r1 = AnnotationBuilder.of(Qual.class).setValue(0).build(); Qual r2 = AnnotationBuilder.of(Qual.class).setValue(1).build(); Qual r3 = AnnotationBuilder.of(Qual.class).setValue(2).build(); Qual r4 = AnnotationBuilder.of(Qual.class).setValue(3).build(); Desire d1 = new MockDesire(null, r1); Desire d2 = new MockDesire(null, r2); Desire d3 = new MockDesire(null, r3); Desire d4 = new MockDesire(null, r4); Desire d5 = new MockDesire(); Desire d6 = new MockDesire(); Desire d7 = new MockDesire(); Satisfaction s1 = new MockSatisfaction(A.class, Arrays.asList(d1, d2)); Satisfaction s2 = new MockSatisfaction(B.class, Arrays.asList(d3, d4)); Satisfaction s3 = new MockSatisfaction(C.class, Arrays.asList(d5)); Satisfaction s4 = new MockSatisfaction(D.class, Arrays.asList(d6)); Satisfaction s5 = new MockSatisfaction(E.class, Arrays.asList(d7)); Satisfaction s6 = new MockSatisfaction(F.class); Satisfaction s7 = new MockSatisfaction(G.class); Desire b1 = new MockDesire(s2); Desire b2 = new MockDesire(s3); Desire b3 = new MockDesire(s4); Desire b4 = new MockDesire(s5); Desire b5 = new MockDesire(s6); Desire b6 = new MockDesire(s7); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(d1, b1), // d1 -> s2 new MockBindRule(d2, b1), // d2 -> s2 new MockBindRule(d3, b3), // d3 -> s4 new MockBindRule(d4, b3), // d4 -> s4 new MockBindRule(d5, b4), // d5 -> s5 new MockBindRule(d6, b4), // d6 -> s5 new MockBindRule(d7, b5)); // d7 -> s6 final Class<?> type = B.class; final MockQualifierMatcher qualifier = MockQualifierMatcher.match(r1); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type, qualifier)), new MockBindRule(d3, b2)); // r1s1:d3 -> s3 final Class<?> type1 = B.class; final MockQualifierMatcher qualifier1 = MockQualifierMatcher.match(r2); final Class<?> type2 = D.class; final MockQualifierMatcher qualifier2 = MockQualifierMatcher.match(r4); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type1, qualifier1), ContextElements.matchType(type2, qualifier2) ), new MockBindRule(d7, b6)); // r2s1,r4s2:d7 -> s7 Desire rootDesire = new MockDesire(s1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); DAGNode<Component, Dependency> rootNode = getRoot(r, rootDesire); // there are 10 nodes, s2, s4 and s5 are duplicated Assert.assertEquals(10 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root // grab all of the nodes in the graph DAGNode<Component, Dependency> n1 = rootNode; DAGNode<Component, Dependency> n2 = getNode(n1, s2, d1); DAGNode<Component, Dependency> on2 = getNode(n1, s2, d2); DAGNode<Component, Dependency> n3 = getNode(n2, s3, d3); DAGNode<Component, Dependency> n4 = getNode(n2, s4, d4); DAGNode<Component, Dependency> on4 = getNode(on2, s4, d3); // should equal n4 DAGNode<Component, Dependency> oon4 = getNode(on2, s4, d4); // should not equal n4 and on4 DAGNode<Component, Dependency> n5 = getNode(n3, s5, d5); DAGNode<Component, Dependency> on5 = getNode(on4, s5, d6); // should equal n5 DAGNode<Component, Dependency> oon5 = getNode(oon4, s5, d6); // should not equal n5 and on5 DAGNode<Component, Dependency> n6 = getNode(n5, s6, d7); DAGNode<Component, Dependency> n7 = getNode(oon5, s7, d7); // make sure that node states are as expected, if they're not null then // they match the satisfaction and desire in the query Assert.assertTrue(n1 != null && n2 != null && n3 != null && n4 != null && n5 != null && n6 != null && n7 != null && on2 != null && on4 != null && on5 != null && oon4 != null && oon5 != null); Assert.assertNotSame(n2, on2); Assert.assertSame(n4, on4); Assert.assertSame(n5, on5); Assert.assertNotSame(n4, oon4); Assert.assertNotSame(n5, oon5); // make sure there aren't any extra edges Assert.assertEquals(2, n1.getOutgoingEdges().size()); Assert.assertEquals(2, n2.getOutgoingEdges().size()); Assert.assertEquals(2, on2.getOutgoingEdges().size()); Assert.assertEquals(1, n3.getOutgoingEdges().size()); Assert.assertEquals(1, n4.getOutgoingEdges().size()); Assert.assertEquals(1, oon4.getOutgoingEdges().size()); Assert.assertEquals(1, n5.getOutgoingEdges().size()); Assert.assertEquals(1, oon5.getOutgoingEdges().size()); Assert.assertEquals(0, n6.getOutgoingEdges().size()); Assert.assertEquals(0, n7.getOutgoingEdges().size()); // special case for root (since the graph adds a synthetic root) Assert.assertEquals(1, r.getGraph().getIncomingEdges(n1).size()); assertThat(r.getGraph().getIncomingEdges(n1).iterator().next().getHead().getLabel(), equalTo(DependencySolver.ROOT_SATISFACTION)); Assert.assertEquals(1, r.getGraph().getIncomingEdges(n2).size()); Assert.assertEquals(1, r.getGraph().getIncomingEdges(on2).size()); Assert.assertEquals(1, r.getGraph().getIncomingEdges(n3).size()); Assert.assertEquals(2, r.getGraph().getIncomingEdges(n4).size()); Assert.assertEquals(1, r.getGraph().getIncomingEdges(oon4).size()); Assert.assertEquals(2, r.getGraph().getIncomingEdges(n5).size()); Assert.assertEquals(1, r.getGraph().getIncomingEdges(oon5).size()); Assert.assertEquals(1, r.getGraph().getIncomingEdges(n6).size()); Assert.assertEquals(1, r.getGraph().getIncomingEdges(n7).size()); } @Test public void testContextBreakingCycleSuccess() throws Exception { // Test that a context that is activated after a certain number of // cycles within a dependency can break out of the cycle and finish resolving Desire d1 = new MockDesire(); Desire d2 = new MockDesire(); Satisfaction s1 = new MockSatisfaction(A.class, Arrays.asList(d1)); Satisfaction s2 = new MockSatisfaction(B.class, Arrays.asList(d2)); Satisfaction os2 = new MockSatisfaction(Bp.class); Desire b1 = new MockDesire(s2); Desire ob1 = new MockDesire(os2); Desire b2 = new MockDesire(s1); // configure bindings so that s1 and s2 cycle for a couple of iterations // until the context s2/s2 is reached, then switches to os2 ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(d1, b1), new MockBindRule(d2, b2)); final Class<?> type = B.class; final MockQualifierMatcher qualifier = MockQualifierMatcher.any(); final Class<?> type1 = B.class; final MockQualifierMatcher qualifier1 = MockQualifierMatcher.any(); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type, qualifier), ContextElements.matchType(type1, qualifier1) ), new MockBindRule(d1, ob1)); Desire rootDesire = new MockDesire(s1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); DAGNode<Component, Dependency> rootNode = getRoot(r, rootDesire); // the resulting graph should be s1->s2->s1->s2->s1->os2 = 6 nodes Assert.assertEquals(6 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertEquals(s1, rootNode.getLabel().getSatisfaction()); // edge s1->s2 by d1 Set<DAGEdge<Component, Dependency>> edges = rootNode.getOutgoingEdges(); Assert.assertEquals(1, edges.size()); DAGEdge<Component, Dependency> e1 = edges.iterator().next(); Assert.assertEquals(s2, e1.getTail().getLabel().getSatisfaction()); Assert.assertEquals(d1, e1.getLabel().getInitialDesire()); // edge s2->s1 by d2 edges = e1.getTail().getOutgoingEdges(); Assert.assertEquals(1, edges.size()); DAGEdge<Component, Dependency> e2 = edges.iterator().next(); Assert.assertEquals(s1, e2.getTail().getLabel().getSatisfaction()); Assert.assertEquals(d2, e2.getLabel().getInitialDesire()); // edge s1->s2 by d1 edges = e2.getTail().getOutgoingEdges(); Assert.assertEquals(1, edges.size()); DAGEdge<Component, Dependency> e3 = edges.iterator().next(); Assert.assertEquals(s2, e3.getTail().getLabel().getSatisfaction()); Assert.assertEquals(d1, e3.getLabel().getInitialDesire()); // edge s2->s1 by d2 edges = e3.getTail().getOutgoingEdges(); Assert.assertEquals(1, edges.size()); DAGEdge<Component, Dependency> e4 = edges.iterator().next(); Assert.assertEquals(s1, e4.getTail().getLabel().getSatisfaction()); Assert.assertEquals(d2, e4.getLabel().getInitialDesire()); // edge s1->os2 by d1 edges = e4.getTail().getOutgoingEdges(); Assert.assertEquals(1, edges.size()); DAGEdge<Component, Dependency> e5 = edges.iterator().next(); Assert.assertEquals(os2, e5.getTail().getLabel().getSatisfaction()); Assert.assertEquals(d1, e5.getLabel().getInitialDesire()); Assert.assertTrue(e5.getTail().getOutgoingEdges().isEmpty()); } @Test public void testTooManyChoicesFilteredByContextSuccess() throws Exception { // Test when too many choices exist, but are limited in scope by contexts // that do not apply, the resolving will succeed Desire d1 = new MockDesire(); Satisfaction s1 = new MockSatisfaction(A.class, Arrays.asList(d1)); Satisfaction s2 = new MockSatisfaction(B.class); Satisfaction os2 = new MockSatisfaction(Bp.class); Desire b1 = new MockDesire(s2); Desire ob1 = new MockDesire(os2); // configure bindings so that d1 has two solutions, but 1 is only active // within a Bp context (which is not possible in this case) ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.put(ContextPattern.any(), new MockBindRule(d1, b1)); final Class<?> type = Bp.class; final MockQualifierMatcher qualifier = MockQualifierMatcher.any(); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type, qualifier)), new MockBindRule(d1, ob1)); Desire rootDesire = new MockDesire(s1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); Assert.assertEquals(2 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root DAGNode<Component, Dependency> n1 = getNode(r.getGraph(), Component.create(s1, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n2 = getNode(r.getGraph(), Component.create(s2, CachePolicy.NO_PREFERENCE)); Assert.assertNotNull(n1); Assert.assertNotNull(n2); Assert.assertEquals(1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).size()); Assert.assertEquals(d1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).iterator().next().getLabel().getInitialDesire()); } @Test public void testLimitedBindRuleApplicationsSuccess() throws Exception { // Test that a bind-rule is properly excluded from subsequent desires // when resolving a desire chain, but a final desire can still be found Desire d1 = new MockDesire(); Satisfaction s1 = new MockSatisfaction(A.class, Arrays.asList(d1)); Satisfaction s2 = new MockSatisfaction(B.class); Desire b1 = new MockDesire(s2); // configure bindings so that s1:d1->d1, d1->b1 ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.put(ContextPattern.any(), new MockBindRule(d1, b1)); final Class<?> type = A.class; final MockQualifierMatcher qualifier = MockQualifierMatcher.any(); bindings.put(ContextPattern.subsequence(ContextElements.matchType(type, qualifier)), new MockBindRule(d1, d1)); Desire rootDesire = new MockDesire(s1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); Assert.assertEquals(2 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root DAGNode<Component, Dependency> n1 = getNode(r.getGraph(), Component.create(s1, CachePolicy.NO_PREFERENCE)); DAGNode<Component, Dependency> n2 = getNode(r.getGraph(), Component.create(s2, CachePolicy.NO_PREFERENCE)); Assert.assertNotNull(n1); Assert.assertNotNull(n2); Assert.assertEquals(1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).size()); Assert.assertEquals(d1, Sets.filter(n1.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(n2))).iterator().next().getLabel().getInitialDesire()); } @Test public void testMultipleRequestsMergeSuccess() throws Exception { // Test that multiple requests to resolve() will update the graph // and share dependency nodes as expected Desire a1 = new MockDesire(); // a's dependency Desire d1 = new MockDesire(); // d's first dependency Desire d2 = new MockDesire(); // d's second dependency Satisfaction sa = new MockSatisfaction(A.class, Arrays.asList(a1)); Satisfaction sap = new MockSatisfaction(Ap.class, Arrays.asList(a1)); // variant of CycleA Satisfaction sd = new MockSatisfaction(D.class, Arrays.asList(d1, d2)); Satisfaction sb = new MockSatisfaction(B.class); Satisfaction sc = new MockSatisfaction(C.class); Desire da = new MockDesire(sa); Desire dap = new MockDesire(sap); // configure bindings so that a1 -> sd, b1 -> sb, b2 -> sc ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(a1, new MockDesire(sd)), new MockBindRule(d1, new MockDesire(sb)), new MockBindRule(d2, new MockDesire(sc))); DependencySolver r = createSolver(bindings.build()); r.resolve(da); r.resolve(dap); DAGNode<Component, Dependency> root = r.getGraph(); Assert.assertEquals(5 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertEquals(2, root.getOutgoingEdges().size()); // da and dap DAGNode<Component, Dependency> na = root.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(da)).getTail(); DAGNode<Component, Dependency> nap = root.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(dap)).getTail(); // sa and sap were different satisfactions, so they should be separate nodes Assert.assertNotSame(na, nap); // the resolved desire for a1, from da DAGNode<Component, Dependency> ra1 = na.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(a1)).getTail(); DAGNode<Component, Dependency> ra1p = nap.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(a1)).getTail(); // verify that both a and ap point to the sb satisfaction, and verify // that sb (and also its children) are properly shared Assert.assertSame(sd, ra1.getLabel().getSatisfaction()); Assert.assertSame(sd, ra1p.getLabel().getSatisfaction()); Assert.assertSame(ra1, ra1p); Predicate<DAGNode<Component, ?>> tgt = DAGNode.labelMatches(Predicates.equalTo(Component.create(sd, CachePolicy.NO_PREFERENCE))); DAGNode<Component, Dependency> node = Iterables.find(r.getGraph().getReachableNodes(), tgt); assertThat(r.getGraph().getIncomingEdges(node), hasSize(2)); } @Test public void testMultipleRequestsNoMergeSuccess() throws Exception { // Test that multiple requests will keep nodes separate as required // by dependency configuration Desire a1 = new MockDesire(); // a's dependency Desire d1 = new MockDesire(); // d's first dependency Desire d2 = new MockDesire(); // d's second dependency Satisfaction sa = new MockSatisfaction(A.class, Arrays.asList(a1)); Satisfaction sap = new MockSatisfaction(Ap.class, Arrays.asList(a1)); // variant of CycleA Satisfaction sd = new MockSatisfaction(D.class, Arrays.asList(d1, d2)); Satisfaction sb = new MockSatisfaction(B.class); Satisfaction sbp = new MockSatisfaction(Bp.class); // variant of B Satisfaction sc = new MockSatisfaction(C.class); Satisfaction scp = new MockSatisfaction(Cp.class); // variant of C Desire da = new MockDesire(sa); Desire dap = new MockDesire(sap); // configure bindings so that a1 -> sd, b1 -> sb, b2 -> sc ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(a1, new MockDesire(sd)), new MockBindRule(d1, new MockDesire(sb)), new MockBindRule(d2, new MockDesire(sc))); final Class<?> type = Ap.class; final MockQualifierMatcher qualifier = MockQualifierMatcher.any(); bindings.putAll(ContextPattern.subsequence(ContextElements.matchType(type, qualifier)), new MockBindRule(d1, new MockDesire(sbp)), new MockBindRule(d2, new MockDesire(scp))); DependencySolver r = createSolver(bindings.build()); r.resolve(da); r.resolve(dap); DAGNode<Component, Dependency> root = r.getGraph(); Assert.assertEquals(8 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertEquals(2, root.getOutgoingEdges().size()); // da and dap DAGNode<Component, Dependency> na = root.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(da)).getTail(); DAGNode<Component, Dependency> nap = root.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(dap)).getTail(); // sa and sap were different satisfactions, so they should be separate nodes Assert.assertNotSame(na, nap); // the resolved desire for a1, from da DAGNode<Component, Dependency> ra1 = na.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(a1)).getTail(); DAGNode<Component, Dependency> ra1p = nap.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(a1)).getTail(); // verify that both ra1 and ra1p are different nodes that both use the // sd satisfaction because sd's dependencies are configured differently Assert.assertNotSame(ra1, ra1p); Assert.assertSame(sd, ra1.getLabel().getSatisfaction()); Assert.assertSame(sd, ra1p.getLabel().getSatisfaction()); Assert.assertSame(sb, ra1.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(d1)).getTail().getLabel().getSatisfaction()); Assert.assertSame(sc, ra1.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(d2)).getTail().getLabel().getSatisfaction()); Assert.assertSame(sbp, ra1p.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(d1)).getTail().getLabel().getSatisfaction()); Assert.assertSame(scp, ra1p.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(d2)).getTail().getLabel().getSatisfaction()); } @Test public void testRequestDependencyMergeSuccess() throws Exception { // Test that a request for a desire already in the graph as a dependency // will have a new edge from the root to that dependency added. Desire a1 = new MockDesire(); // a's dependency Desire d1 = new MockDesire(); // d's first dependency Desire d2 = new MockDesire(); // d's second dependency Satisfaction sa = new MockSatisfaction(A.class, Arrays.asList(a1)); Satisfaction sd = new MockSatisfaction(D.class, Arrays.asList(d1, d2)); Satisfaction sb = new MockSatisfaction(B.class); Satisfaction sc = new MockSatisfaction(C.class); Desire da = new MockDesire(sa); Desire dd = new MockDesire(sd); // configure bindings so that a1 -> sd, b1 -> sb, b2 -> sc ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(a1, new MockDesire(sd)), new MockBindRule(d1, new MockDesire(sb)), new MockBindRule(d2, new MockDesire(sc))); DependencySolver r = createSolver(bindings.build()); r.resolve(da); r.resolve(dd); DAGNode<Component, Dependency> root = r.getGraph(); Assert.assertEquals(4 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertEquals(2, root.getOutgoingEdges().size()); // da and dd DAGNode<Component, Dependency> na = root.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(da)).getTail(); DAGNode<Component, Dependency> nd = root.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(dd)).getTail(); // additionally verify that there is an edge going from na to nd Assert.assertEquals(1, Sets.filter(na.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(nd))).size()); Assert.assertSame(nd, na.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(a1)).getTail()); } @Test public void testRequestDependencyNoMergeSuccess() throws Exception { // Test that a request for a desire already in the graph as a dependency, // will create a new node if the dependency has a different configuration // because of context-specific bind rules Desire a1 = new MockDesire(); // a's dependency Desire d1 = new MockDesire(); // d's first dependency Desire d2 = new MockDesire(); // d's second dependency Satisfaction sa = new MockSatisfaction(A.class, Arrays.asList(a1)); Satisfaction sd = new MockSatisfaction(D.class, Arrays.asList(d1, d2)); Satisfaction sb = new MockSatisfaction(B.class); Satisfaction sbp = new MockSatisfaction(Bp.class); // variant of B Satisfaction sc = new MockSatisfaction(C.class); Satisfaction scp = new MockSatisfaction(Cp.class); // variant of C Desire da = new MockDesire(sa); Desire dd = new MockDesire(sd); // configure bindings so that a1 -> sd, b1 -> sb, b2 -> sc ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(a1, new MockDesire(sd)), new MockBindRule(d1, new MockDesire(sbp)), new MockBindRule(d2, new MockDesire(scp))); final Class<?> type = A.class; final MockQualifierMatcher qualifier = MockQualifierMatcher.any(); bindings.putAll(ContextPattern.subsequence(ContextElements.matchType(type, qualifier)), new MockBindRule(d1, new MockDesire(sb)), new MockBindRule(d2, new MockDesire(sc))); DependencySolver r = createSolver(bindings.build()); r.resolve(da); r.resolve(dd); DAGNode<Component, Dependency> root = r.getGraph(); Assert.assertEquals(7 + 1, r.getGraph().getReachableNodes().size()); // add one for synthetic root Assert.assertEquals(2, root.getOutgoingEdges().size()); // da and dd // resolved root desire nodes DAGNode<Component, Dependency> na = root.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(da)).getTail(); DAGNode<Component, Dependency> nd = root.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(dd)).getTail(); // make sure that there is no edge between na and nd Assert.assertTrue(Sets.filter(na.getOutgoingEdges(), DAGEdge.tailMatches(Predicates.equalTo(nd))).isEmpty()); // look up dependency for na (which is also the sd satisfaction) DAGNode<Component, Dependency> nad = na.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(a1)).getTail(); // verify that the two sd nodes are different and have different edge // configurations Assert.assertNotSame(nd, nad); Assert.assertSame(sd, nd.getLabel().getSatisfaction()); Assert.assertSame(sd, nad.getLabel().getSatisfaction()); Assert.assertSame(sb, nad.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(d1)).getTail().getLabel().getSatisfaction()); Assert.assertSame(sc, nad.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(d2)).getTail().getLabel().getSatisfaction()); Assert.assertSame(sbp, nd.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(d1)).getTail().getLabel().getSatisfaction()); Assert.assertSame(scp, nd.getOutgoingEdgeWithLabel(Dependency.hasInitialDesire(d2)).getTail().getLabel().getSatisfaction()); } @Test(expected=UnresolvableDependencyException.class) public void testLimitedBindRuleApplicationsFail() throws Exception { // Test that a bind-rule is properly excluded form subsequent desires // but that leaves no applicable bindings so resolving fails Desire d1 = new MockDesire(); Satisfaction s1 = new MockSatisfaction(A.class, Arrays.asList(d1)); // configure bindings so that d1->d1 so binding fails ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.put(ContextPattern.any(), new MockBindRule(d1, d1)); Desire rootDesire = new MockDesire(s1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); } @Test(expected=CyclicDependencyException.class) public void testCyclicDependenciesFail() throws Exception { // Test that a cyclic dependency is properly caught and resolving // fails before a stack overflow Desire d1 = new MockDesire(); Desire d2 = new MockDesire(); Satisfaction s1 = new MockSatisfaction(A.class, Arrays.asList(d1)); Satisfaction s2 = new MockSatisfaction(B.class, Arrays.asList(d2)); Desire b1 = new MockDesire(s2); Desire b2 = new MockDesire(s1); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(d1, b1), new MockBindRule(d2, b2)); Desire rootDesire = new MockDesire(s1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); } @Test(expected=MultipleBindingsException.class) public void testTooManyBindRulesFail() throws Exception { // Test that providing too many choices for bind rules throws an exception Desire d1 = new MockDesire(); Satisfaction s1 = new MockSatisfaction(A.class, Arrays.asList(d1)); Satisfaction s2 = new MockSatisfaction(B.class); Satisfaction s3 = new MockSatisfaction(C.class); Desire b1 = new MockDesire(s2); Desire ob1 = new MockDesire(s3); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(d1, b1), new MockBindRule(d1, ob1)); Desire rootDesire = new MockDesire(s1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); } @Test(expected=UnresolvableDependencyException.class) public void testUnsatisfiableDesireFail() throws Exception { // Test that a chain of desires that cannot be satisfied throws an exception Desire d1 = new MockDesire(); Desire d2 = new MockDesire(); Desire d3 = new MockDesire(); Satisfaction s1 = new MockSatisfaction(A.class, Arrays.asList(d1)); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(d1, d2), new MockBindRule(d2, d3)); Desire rootDesire = new MockDesire(s1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); } @Test(expected=UnresolvableDependencyException.class) public void testNoBindRulesFail() throws Exception { // Test that not providing applicable bind rules will throw an exception, // even if other bind rules are given Desire d1 = new MockDesire(); Desire d2 = new MockDesire(); Satisfaction s1 = new MockSatisfaction(A.class, Arrays.asList(d1)); Desire b2 = new MockDesire(); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.put(ContextPattern.any(), new MockBindRule(d2, b2)); Desire rootDesire = new MockDesire(s1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); } @Test(expected=UnresolvableDependencyException.class) public void testNonLeafSatisfiableDesireFail() throws Exception { // Test that a chain of desires, where an intermediate desire is // satisfiable but the leaf node is not, still throws an exception Desire d1 = new MockDesire(); Satisfaction s1 = new MockSatisfaction(A.class, Arrays.asList(d1)); Satisfaction s2 = new MockSatisfaction(B.class); Desire b1 = new MockDesire(s2); Desire b2 = new MockDesire(); ImmutableListMultimap.Builder<ContextMatcher, BindRule> bindings = ImmutableListMultimap.builder(); bindings.putAll(ContextPattern.any(), new MockBindRule(d1, b1), new MockBindRule(b1, b2)); Desire rootDesire = new MockDesire(s1); DependencySolver r = createSolver(bindings.build()); r.resolve(rootDesire); } // Find the node for s connected to p by the given desire, d private DAGNode<Component, Dependency> getNode(DAGNode<Component, Dependency> graph, Satisfaction s, Desire d) { for (DAGEdge<Component, Dependency> e: graph.getOutgoingEdges()) { if (e.getLabel().getInitialDesire().equals(d) && e.getTail().getLabel().getSatisfaction().equals(s)) { return e.getTail(); } } return null; } private DAGNode<Component, Dependency> getNode(DAGNode<Component, Dependency> g, Component s) { Predicate<DAGNode<Component, ?>> pred = DAGNode.labelMatches(Predicates.equalTo(s)); return Iterables.find(g.getReachableNodes(), pred, null); } @Qualifier @Retention(RetentionPolicy.RUNTIME) public static @interface Qual { int value(); } private static class A {} private static class B {} private static class C {} private static class D {} private static class E {} private static class F {} private static class G {} private static class Ap extends A {} private static class Bp extends B {} private static class Cp extends C {} }