[FELIX-4825] The resolver may return wires to unresolved resources in some cases

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1667219 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/resolver/src/main/java/org/apache/felix/resolver/Candidates.java b/resolver/src/main/java/org/apache/felix/resolver/Candidates.java
index 4cc9f18..e5501d4 100644
--- a/resolver/src/main/java/org/apache/felix/resolver/Candidates.java
+++ b/resolver/src/main/java/org/apache/felix/resolver/Candidates.java
@@ -328,6 +328,22 @@
         {
             // Record that the revision was successfully populated.
             m_populateResultCache.put(resource, Boolean.TRUE);
+            // FELIX-4825: Verify candidate map in case of cycles and optional requirements
+            for (Iterator<Map.Entry<Requirement, List<Capability>>> it = localCandidateMap.entrySet().iterator(); it.hasNext();)
+            {
+                Map.Entry<Requirement, List<Capability>> entry = it.next();
+                for (Iterator<Capability> it2 = entry.getValue().iterator(); it2.hasNext();)
+                {
+                    if (m_populateResultCache.get(it2.next().getResource()) instanceof ResolutionException)
+                    {
+                        it2.remove();
+                    }
+                }
+                if (entry.getValue().isEmpty())
+                {
+                    it.remove();
+                }
+            }
             // Merge local candidate map into global candidate map.
             if (localCandidateMap.size() > 0)
             {
diff --git a/resolver/src/test/java/org/apache/felix/resolver/test/ResolverTest.java b/resolver/src/test/java/org/apache/felix/resolver/test/ResolverTest.java
index 5f5eeba..926b42c 100644
--- a/resolver/src/test/java/org/apache/felix/resolver/test/ResolverTest.java
+++ b/resolver/src/test/java/org/apache/felix/resolver/test/ResolverTest.java
@@ -19,6 +19,7 @@
 package org.apache.felix.resolver.test;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -32,7 +33,9 @@
 
 import org.apache.felix.resolver.Logger;
 import org.apache.felix.resolver.ResolverImpl;
+import org.junit.Ignore;
 import org.junit.Test;
+import org.osgi.framework.Constants;
 import org.osgi.framework.namespace.BundleNamespace;
 import org.osgi.framework.namespace.HostNamespace;
 import org.osgi.framework.namespace.IdentityNamespace;
@@ -355,6 +358,41 @@
         assertEquals("(osgi.identity=F2)", bReq.getDirectives().get("filter"));
     }
 
+    @Test
+    public void testScenario8() throws Exception
+    {
+        Resolver resolver = new ResolverImpl(new Logger(Logger.LOG_DEBUG));
+
+        Map<Resource, Wiring> wirings = new HashMap<Resource, Wiring>();
+        Map<Requirement, List<Capability>> candMap = new HashMap<Requirement, List<Capability>>();
+        List<Resource> mandatory = populateScenario8(wirings, candMap);
+        ResolveContextImpl rci = new ResolveContextImpl(wirings, candMap, mandatory, Collections.<Resource> emptyList());
+
+        Map<Resource, List<Wire>> wireMap = resolver.resolve(rci);
+
+        Resource res2 = findResource("res2", wireMap.keySet());
+        Resource res4 = findResource("res4", wireMap.keySet());
+        Resource res5 = findResource("res5", wireMap.keySet());
+
+        assertNotNull(res2);
+        assertNotNull(res4);
+        assertNotNull(res5);
+
+        List<Wire> wires2 = wireMap.get(res2);
+        assertEquals(2, wires2.size());
+        // should be wired to res4 and res5
+
+        List<Wire> wires4 = wireMap.get(res4);
+        assertEquals(1, wires4.size());
+        // should be wired to res5
+
+        List<Wire> wires5 = wireMap.get(res5);
+        assertEquals(0, wires5.size());
+        // should not be wired to any of its optional requirements to res6
+
+        assertEquals(3, wireMap.size());
+    }
+
     private static String getResourceName(Resource r)
     {
         return r.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE).get(0).getAttributes()
@@ -638,4 +676,63 @@
         resources.add(b1);
         return resources;
     }
+
+    private static List<Resource> populateScenario8(Map<Resource, Wiring> wirings, Map<Requirement, List<Capability>> candMap)
+    {
+        ResourceImpl res2 = new ResourceImpl("res2");
+        Requirement req25 = addReq(res2, IdentityNamespace.IDENTITY_NAMESPACE, "res5");
+        Requirement req24 = addReq(res2, IdentityNamespace.IDENTITY_NAMESPACE, "res4");
+        Requirement req23 = addReq(res2, IdentityNamespace.IDENTITY_NAMESPACE, "res3", true);
+
+        ResourceImpl res3 = new ResourceImpl("res3");
+        Requirement req32 = addReq(res3, IdentityNamespace.IDENTITY_NAMESPACE, "res2");
+        Requirement req3x = addReq(res3, "foo", "bar");
+
+        ResourceImpl res4 = new ResourceImpl("res4");
+        Requirement req45 = addReq(res4, IdentityNamespace.IDENTITY_NAMESPACE, "res5");
+
+        ResourceImpl res5 = new ResourceImpl("res5");
+        Requirement req5x1 = addReq(res5, BundleNamespace.BUNDLE_NAMESPACE, "package1", true);
+        Requirement req5x2 = addReq(res5, BundleNamespace.BUNDLE_NAMESPACE, "package2", true);
+
+        ResourceImpl res6 = new ResourceImpl("res6");
+        Capability cap6x1 = addCap(res6, BundleNamespace.BUNDLE_NAMESPACE, "package1");
+        Capability cap6x2 = addCap(res6, BundleNamespace.BUNDLE_NAMESPACE, "package2");
+        Requirement req63 = addReq(res6, IdentityNamespace.IDENTITY_NAMESPACE, "res3");
+
+        candMap.put(req25, res5.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE));
+        candMap.put(req24, res4.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE));
+        candMap.put(req23, res3.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE));
+        candMap.put(req32, res2.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE));
+        candMap.put(req45, res5.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE));
+        candMap.put(req63, res3.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE));
+        candMap.put(req3x, Arrays.<Capability>asList());
+        candMap.put(req5x1, Arrays.<Capability>asList(cap6x1));
+        candMap.put(req5x2, Arrays.<Capability>asList(cap6x2));
+        return Arrays.<Resource>asList(res2);
+    }
+
+    private static Capability addCap(ResourceImpl res, String namespace, String value)
+    {
+        GenericCapability cap = new GenericCapability(res, namespace);
+        cap.addAttribute(namespace, value);
+        res.addCapability(cap);
+        return cap;
+    }
+
+    private static Requirement addReq(ResourceImpl res, String namespace, String value)
+    {
+        return addReq(res, namespace, value, false);
+    }
+
+    private static Requirement addReq(ResourceImpl res, String namespace, String value, boolean optional)
+    {
+        GenericRequirement req = new GenericRequirement(res, namespace);
+        req.addDirective(Namespace.REQUIREMENT_FILTER_DIRECTIVE, "(" + namespace + "=" + value + ")");
+        if (optional) {
+            req.addDirective(Constants.RESOLUTION_DIRECTIVE, Constants.RESOLUTION_OPTIONAL);
+        }
+        res.addRequirement(req);
+        return req;
+    }
 }