Bug 514149 - [resolver] issues resolving with substitutable exports

Fix by checking if removing a candidate will substitute the only
provider which satisfies a dependency.

Change-Id: Iae05477ce7ddef9ee036ac8e7cc5000c816d042f
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java
index 29b2b7b..44c96a3 100755
--- a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java
@@ -1203,7 +1203,54 @@
     public boolean canRemoveCandidate(Requirement req)
     {
         CandidateSelector candidates = m_candidateMap.get(req);
-        return ((candidates != null) && (candidates.getRemainingCandidateCount() > 1 || Util.isOptional(req)));
+        if (candidates != null)
+        {
+            Capability current = candidates.getCurrentCandidate();
+            if (current != null)
+            {
+                // IMPLEMENTATION NOTE:
+                // Here we check for a req that is used for a substitutable export.
+                // If we find a substitutable req then an extra check is done to see
+                // if the substitutable capability is currently depended on as the
+                // only provider of some other requirement.  If it is then we do not
+                // allow the candidate to be removed.
+                // This is done because of the way we attempt to reduce permutations
+                // checked by permuting all used requirements that conflict with a
+                // directly imported/required capability in one go.
+                // If we allowed these types of substitutable requirements to move
+                // to the next capability then the permutation would be thrown out
+                // because it would cause some other resource to not resolve.
+                // That in turn would throw out the complete permutation along with
+                // any follow on permutations that could have resulted.
+                // See ResolverImpl::checkPackageSpaceConsistency
+
+                // Check if the current candidate is substitutable by the req;
+                // This check is necessary here because
+                if (req.equals(m_subtitutableMap.get(current)))
+                {
+                    // this is a substitute req,
+                    // make sure there is not an existing dependency that would fail if we substitute
+                    Set<Requirement> dependents = m_dependentMap.get(current);
+                    if (dependents != null)
+                    {
+                        for (Requirement dependent : dependents)
+                        {
+                            CandidateSelector dependentSelector = m_candidateMap.get(dependent);
+                            // If the dependent selector only has one capability left then we know it is
+                            // using the current candidate and has no options left
+                            if (dependentSelector != null && dependentSelector.getRemainingCandidateCount() <= 1)
+                            {
+                                // return false since we do not want to allow this requirement
+                                // to substitute the capability
+                                return false;
+                            }
+                        }
+                    }
+                }
+            }
+            return candidates.getRemainingCandidateCount() > 1 || Util.isOptional(req);
+        }
+        return false;
     }
 
     static class DynamicImportFailed extends ResolutionError {
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java
index 60beca8..dd7b7c5 100755
--- a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java
@@ -1288,8 +1288,6 @@
         Packages pkgs = resourcePkgMap.get(resource);
 
         ResolutionError rethrow = null;
-        Candidates permutation = null;
-        Set<Requirement> mutated = null;
 
         // Check for conflicting imports from fragments.
         // TODO: Is this only needed for imports or are generic and bundle requirements also needed?
@@ -1332,6 +1330,16 @@
             }
         }
 
+        // IMPLEMENTATION NOTE:
+        // Below we track the mutated reqs that have been permuted
+        // in a single candidates permutation.  This permutation may contain a
+        // delta of several reqs which conflict with a directly imported/required candidates.
+        // When several reqs are permuted at the same time this reduces the number of solutions tried.
+        // See the method Candidates::canRemoveCandidate for a case where substitutions must be checked
+        // because of this code that may permute multiple reqs in on candidates permutation.
+        Set<Requirement> mutated = null;
+        Candidates permutation = null;
+
         // Check if there are any uses conflicts with exported packages.
         for (Entry<String, Blame> entry : pkgs.m_exportedPkgs.fast())
         {