[FELIX-4942] Slightly refactor Candidates.populate() to remove the recursion.
[FELIX-4942] Populate the dependent map at the same time.

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1690731 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 8f56fdd..578706f 100644
--- a/resolver/src/main/java/org/apache/felix/resolver/Candidates.java
+++ b/resolver/src/main/java/org/apache/felix/resolver/Candidates.java
@@ -24,6 +24,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -50,8 +51,17 @@
 
 class Candidates
 {
-    public static final int MANDATORY = 0;
-    public static final int OPTIONAL = 1;
+    static class PopulateResult {
+        boolean success;
+        ResolutionError error;
+        List<Requirement> remaining;
+        Map<Requirement, List<Capability>> candidates;
+
+        @Override
+        public String toString() {
+            return success ? "true" : error != null ? error.getMessage() : "???";
+        }
+    }
 
     private final Set<Resource> m_mandatoryResources;
     // Maps a capability to requirements that match it.
@@ -62,10 +72,7 @@
     // when a revision being resolved has fragments to attach to it.
     private final Map<Resource, WrappedResource> m_allWrappedHosts;
     // Map used when populating candidates to hold intermediate and final results.
-    private final OpenHashMap<Resource, Object> m_populateResultCache;
-
-    // Flag to signal if fragments are present in the candidate map.
-    private boolean m_fragmentsPresent = false;
+    private final OpenHashMap<Resource, PopulateResult> m_populateResultCache;
 
     private final Map<Resource, Boolean> m_validOnDemandResources;
 
@@ -81,8 +88,7 @@
         OpenHashMapSet<Capability, Requirement> dependentMap,
         OpenHashMapList<Requirement, Capability> candidateMap,
         Map<Resource, WrappedResource> wrappedHosts,
-        OpenHashMap<Resource, Object> populateResultCache,
-        boolean fragmentsPresent,
+        OpenHashMap<Resource, PopulateResult> populateResultCache,
         Map<Resource, Boolean> onDemandResources,
         Map<Capability, Requirement> substitutableMap,
         OpenHashMapSet<Requirement, Capability> delta)
@@ -92,7 +98,6 @@
         m_candidateMap = candidateMap;
         m_allWrappedHosts = wrappedHosts;
         m_populateResultCache = populateResultCache;
-        m_fragmentsPresent = fragmentsPresent;
         m_validOnDemandResources = onDemandResources;
         m_subtitutableMap = substitutableMap;
         m_delta = delta;
@@ -107,7 +112,7 @@
         m_dependentMap = new OpenHashMapSet<Capability, Requirement>();
         m_candidateMap = new OpenHashMapList<Requirement, Capability>();
         m_allWrappedHosts = new HashMap<Resource, WrappedResource>();
-        m_populateResultCache = new OpenHashMap<Resource, Object>();
+        m_populateResultCache = new OpenHashMap<Resource, PopulateResult>();
         m_validOnDemandResources = validOnDemandResources;
         m_subtitutableMap = new OpenHashMap<Capability, Requirement>();
         m_delta = new OpenHashMapSet<Requirement, Capability>(3);
@@ -123,259 +128,139 @@
      * original Candidates permutation.
      * @return the delta
      */
-    public Object getDelta() {
+    public Object getDelta()
+    {
         return m_delta;
     }
 
-    /**
-     * Populates candidates for the specified revision. How a revision is
-     * resolved depends on its resolution type as follows:
-     * <ul>
-     * <li><tt>MANDATORY</tt> - must resolve and failure to do so throws an
-     * exception.</li>
-     * <li><tt>OPTIONAL</tt> - attempt to resolve, but no exception is thrown if
-     * the resolve fails.</li>
-     * <li><tt>ON_DEMAND</tt> - only resolve on demand; this only applies to
-     * fragments and will only resolve a fragment if its host is already
-     * selected as a candidate.</li>
-     * </ul>
-     *
-     * @param rc the resolve context used for populating the candidates.
-     * @param resource the resource whose candidates should be populated.
-     * @param resolution indicates the resolution type.
-     */
-    public final ResolutionError populate(
-        ResolveContext rc, Resource resource, int resolution)
+    public void addMandatoryResources(Collection<Resource> resources)
     {
-        // Get the current result cache value, to make sure the revision
-        // hasn't already been populated.
-        Object cacheValue = m_populateResultCache.get(resource);
-        // Has been unsuccessfully populated.
-        if (cacheValue instanceof ResolutionError)
-        {
-            return null;
-        }
-        // Has been successfully populated.
-        else if (cacheValue instanceof Boolean)
-        {
-            return null;
-        }
-
-        // We will always attempt to populate fragments, since this is necessary
-        // for ondemand attaching of fragment. However, we'll only attempt to
-        // populate optional non-fragment revisions if they aren't already
-        // resolved.
-        boolean isFragment = Util.isFragment(resource);
-        if (!isFragment && rc.getWirings().containsKey(resource))
-        {
-            return null;
-        }
-
-        if (resolution == MANDATORY)
-        {
-            m_mandatoryResources.add(resource);
-        }
-        // Try to populate candidates for the optional revision.
-        ResolutionError error = populateResource(rc, resource);
-        // Only throw an exception if resolution is mandatory.
-        if (error != null && resolution == MANDATORY)
-        {
-            return error;
-        }
-        return null;
+        m_mandatoryResources.addAll(resources);
     }
 
-    /**
-     * Populates candidates for the specified revision.
-     *
-     * @param rc the resolver state used for populating the candidates.
-     * @param resource the revision whose candidates should be populated.
-     */
-// TODO: FELIX3 - Modify to not be recursive.
-    @SuppressWarnings("unchecked")
-    private ResolutionError populateResource(ResolveContext rc, Resource resource)
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public ResolutionError populate(ResolveContext rc, Collection<Resource> resources)
     {
-        // Determine if we've already calculated this revision's candidates.
-        // The result cache will have one of three values:
-        //   1. A resolve exception if we've already attempted to populate the
-        //      revision's candidates but were unsuccessful.
-        //   2. Boolean.TRUE indicating we've already attempted to populate the
-        //      revision's candidates and were successful.
-        //   3. An array containing the cycle count, current map of candidates
-        //      for already processed requirements, and a list of remaining
-        //      requirements whose candidates still need to be calculated.
-        // For case 1, rethrow the exception. For case 2, simply return immediately.
-        // For case 3, this means we have a cycle so we should continue to populate
-        // the candidates where we left off and not record any results globally
-        // until we've popped completely out of the cycle.
-
-        // Keeps track of the number of times we've reentered this method
-        // for the current revision.
-        Integer cycleCount = null;
-
-        // Keeps track of the candidates we've already calculated for the
-        // current revision's requirements.
-        Map<Requirement, List<Capability>> localCandidateMap = null;
-
-        // Keeps track of the current revision's requirements for which we
-        // haven't yet found candidates.
-        List<Requirement> remainingReqs = null;
-
-        // Get the cache value for the current revision.
-        Object cacheValue = m_populateResultCache.get(resource);
-
-        // This is case 1.
-        if (cacheValue instanceof ResolutionError)
+        Set<Resource> toRemove = new HashSet<Resource>();
+        LinkedList<Resource> toPopulate = new LinkedList<Resource>(resources);
+        while (!toPopulate.isEmpty())
         {
-            return (ResolutionError) cacheValue;
-        }
-        // This is case 2.
-        else if (cacheValue instanceof Boolean)
-        {
-            return null;
-        }
-        // This is case 3.
-        else if (cacheValue != null)
-        {
-            // Increment and get the cycle count.
-            cycleCount = (Integer) (((Object[]) cacheValue)[0] = (Integer) ((Object[]) cacheValue)[0] + 1);
-            // Get the already populated candidates.
-            localCandidateMap = (Map) ((Object[]) cacheValue)[1];
-            // Get the remaining requirements.
-            remainingReqs = (List) ((Object[]) cacheValue)[2];
-        }
-
-        // If there is no cache value for the current revision, then this is
-        // the first time we are attempting to populate its candidates, so
-        // do some one-time checks and initialization.
-        if ((remainingReqs == null) && (localCandidateMap == null))
-        {
-            // Record cycle count.
-            cycleCount = 0;
-
-            // Create a local map for populating candidates first, just in case
-            // the revision is not resolvable.
-            localCandidateMap = new HashMap<Requirement, List<Capability>>();
-
-            // Create a modifiable list of the revision's requirements.
-            remainingReqs = new ArrayList<Requirement>(resource.getRequirements(null));
-
-            // Add these value to the result cache so we know we are
-            // in the middle of populating candidates for the current
-            // revision.
-            m_populateResultCache.put(resource,
-                cacheValue = new Object[] { cycleCount, localCandidateMap, remainingReqs });
-        }
-
-        // If we have requirements remaining, then find candidates for them.
-        while (!remainingReqs.isEmpty())
-        {
-            Requirement req = remainingReqs.remove(0);
-
-            // Ignore non-effective and dynamic requirements.
-            String resolution = req.getDirectives()
-                .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
-            if (!rc.isEffective(req)
-                || ((resolution != null)
-                && resolution.equals(PackageNamespace.RESOLUTION_DYNAMIC)))
+            Resource resource = toPopulate.getFirst();
+            // Get cached result
+            PopulateResult result = m_populateResultCache.get(resource);
+            if (result == null)
+            {
+                result = new PopulateResult();
+                result.candidates = new OpenHashMap<Requirement, List<Capability>>();
+                result.remaining = new ArrayList<Requirement>(resource.getRequirements(null));
+                m_populateResultCache.put(resource, result);
+            }
+            if (result.success || result.error != null)
+            {
+                toPopulate.removeFirst();
+                continue;
+            }
+            if (result.remaining.isEmpty())
+            {
+                toPopulate.removeFirst();
+                result.success = true;
+                addCandidates(result.candidates);
+                result.candidates = null;
+                result.remaining = null;
+                if ((rc instanceof FelixResolveContext) && !Util.isFragment(resource))
+                {
+                    Collection<Resource> ondemandFragments = ((FelixResolveContext) rc).getOndemandResources(resource);
+                    for (Resource fragment : ondemandFragments)
+                    {
+                        Boolean valid = m_validOnDemandResources.get(fragment);
+                        if (valid == null)
+                        {
+                            // Mark this resource as a valid on demand resource
+                            m_validOnDemandResources.put(fragment, Boolean.TRUE);
+                            valid = Boolean.TRUE;
+                        }
+                        if (valid)
+                        {
+                            // This resource is a valid on demand resource;
+                            // populate it now, consider it optional
+                            toPopulate.addFirst(fragment);
+                        }
+                    }
+                }
+                continue;
+            }
+            // We have a requirement to process
+            Requirement requirement = result.remaining.remove(0);
+            if (!isEffective(rc, requirement))
             {
                 continue;
             }
-
-            // Process the candidates, removing any candidates that
-            // cannot resolve.
-            List<Capability> candidates = rc.findProviders(req);
-            ResolutionError rethrow = processCandidates(rc, resource, candidates);
-
-            // First, due to cycles, makes sure we haven't already failed in
-            // a deeper recursion.
-            Object result = m_populateResultCache.get(resource);
-            if (result instanceof ResolutionError)
-            {
-                return (ResolutionError) result;
-            }
-            // Next, if are no candidates remaining and the requirement is not
-            // not optional, then record and throw a resolve exception.
-            else if (candidates.isEmpty() && !Util.isOptional(req))
+            List<Capability> candidates = rc.findProviders(requirement);
+            LinkedList<Resource> newToPopulate = new LinkedList<Resource>();
+            ResolutionError thrown = processCandidates(rc, newToPopulate, requirement, candidates);
+             if (candidates.isEmpty() && !Util.isOptional(requirement))
             {
                 if (Util.isFragment(resource) && rc.getWirings().containsKey(resource))
                 {
                     // This is a fragment that is already resolved and there is no unresolved hosts to attach it to.
-                    m_populateResultCache.put(resource, Boolean.TRUE);
-                    return null;
+                    result.success = true;
                 }
-                rethrow = new MissingRequirementError(req, rethrow);
-                m_populateResultCache.put(resource, rethrow);
-                return rethrow;
+                else
+                {
+                    result.error = new MissingRequirementError(requirement, thrown);
+                    toRemove.add(resource);
+                }
+                toPopulate.removeFirst();
             }
-            // Otherwise, if we actually have candidates for the requirement, then
-            // add them to the local candidate map.
-            else if (candidates.size() > 0)
+            else
             {
-                localCandidateMap.put(req, candidates);
+                if (!candidates.isEmpty())
+                {
+                    result.candidates.put(requirement, candidates);
+                }
+                if (!newToPopulate.isEmpty())
+                {
+                    toPopulate.addAll(0, newToPopulate);
+                }
             }
         }
 
-        // If we are exiting from a cycle then decrement
-        // cycle counter, otherwise record the result.
-        if (cycleCount > 0)
+        while (!toRemove.isEmpty())
         {
-            ((Object[]) cacheValue)[0] = cycleCount - 1;
-        }
-        else if (cycleCount == 0)
-        {
-            // 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 ResolutionError)
-                    {
-                        it2.remove();
-                    }
-                }
-                if (entry.getValue().isEmpty())
-                {
-                    it.remove();
-                }
-            }
-            // Merge local candidate map into global candidate map.
-            if (localCandidateMap.size() > 0)
-            {
-                add(localCandidateMap);
-            }
-            if ((rc instanceof FelixResolveContext) && !Util.isFragment(resource))
-            {
-                Collection<Resource> ondemandFragments = ((FelixResolveContext) rc).getOndemandResources(resource);
-                for (Resource fragment : ondemandFragments)
-                {
-                    Boolean valid = m_validOnDemandResources.get(fragment);
-                    if (valid == null)
-                    {
-                        // Mark this resource as a valid on demand resource
-                        m_validOnDemandResources.put(fragment, Boolean.TRUE);
-                        valid = Boolean.TRUE;
-                    }
-                    if (valid)
-                    {
-                        // This resource is a valid on demand resource;
-                        // populate it now, consider it optional
-                        populate(rc, fragment, OPTIONAL);
-                    }
-                }
-            }
+            Iterator<Resource> iterator = toRemove.iterator();
+            Resource resource = iterator.next();
+            iterator.remove();
+            remove(resource, toRemove);
         }
         return null;
     }
 
+    private boolean isEffective(ResolveContext rc, Requirement req) {
+        if (!rc.isEffective(req)) {
+            return false;
+        }
+        String res = req.getDirectives().get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
+        return !PackageNamespace.RESOLUTION_DYNAMIC.equals(res);
+    }
+
+    private boolean isMandatory(ResolveContext rc, Requirement requirement) {
+        // The requirement is optional
+        if (Util.isOptional(requirement)) {
+            return false;
+        }
+        // This is a fragment that is already resolved and there is no unresolved hosts to attach it to
+        Resource resource = requirement.getResource();
+        if (Util.isFragment(resource) && rc.getWirings().containsKey(resource)) {
+            return false;
+        }
+        return true;
+    }
+
     private void populateSubstitutables()
     {
-        for (Map.Entry<Resource, Object> populated : m_populateResultCache.fast())
+        for (Map.Entry<Resource, PopulateResult> populated : m_populateResultCache.fast())
         {
-            if (populated.getValue() instanceof Boolean)
+            if (populated.getValue().success)
             {
                 populateSubstitutables(populated.getKey());
             }
@@ -385,31 +270,33 @@
     private void populateSubstitutables(Resource resource)
     {
         // Collect the package names exported
-        List<Capability> packageExports = resource.getCapabilities(PackageNamespace.PACKAGE_NAMESPACE);
-        if (packageExports.isEmpty())
-        {
-            return;
-        }
-        List<Requirement> packageImports = resource.getRequirements(PackageNamespace.PACKAGE_NAMESPACE);
-        if (packageImports.isEmpty())
-        {
-            return;
-        }
         OpenHashMap<String, List<Capability>> exportNames = new OpenHashMap<String, List<Capability>>() {
             @Override
             protected List<Capability> compute(String s) {
                 return new ArrayList<Capability>(1);
             }
         };
-        for (Capability packageExport : packageExports)
+        for (Capability packageExport : resource.getCapabilities(null))
         {
+            if (!PackageNamespace.PACKAGE_NAMESPACE.equals(packageExport.getNamespace()))
+            {
+                continue;
+            }
             String packageName = (String) packageExport.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
             List<Capability> caps = exportNames.getOrCompute(packageName);
             caps.add(packageExport);
         }
-        // Check if any requirements substitute one of the exported packages
-        for (Requirement req : packageImports)
+        if (exportNames.isEmpty())
         {
+            return;
+        }
+        // Check if any requirements substitute one of the exported packages
+        for (Requirement req : resource.getRequirements(null))
+        {
+            if (!PackageNamespace.PACKAGE_NAMESPACE.equals(req.getNamespace()))
+            {
+                continue;
+            }
             List<Capability> substitutes = m_candidateMap.get(req);
             if (substitutes != null && !substitutes.isEmpty())
             {
@@ -453,14 +340,6 @@
         // Remove any substituted exports from candidates
         for (Map.Entry<Capability, Integer> substituteStatus : substituteStatuses.fast())
         {
-            if (substituteStatus.getValue() == SUBSTITUTED)
-            {
-                if (m_dependentMap.isEmpty())
-                {
-                    // make sure the dependents are populated
-                    populateDependents();
-                }
-            }
             // add a permutation that imports a different candidate for the substituted if possible
             Requirement substitutedReq = m_subtitutableMap.get(substituteStatus.getKey());
             if (substitutedReq != null)
@@ -579,12 +458,15 @@
 
         // Process the candidates, removing any candidates that
         // cannot resolve.
-        ResolutionError rethrow = processCandidates(rc, resource, candidates);
+        // TODO: verify the two following statements
+        LinkedList<Resource> toPopulate = new LinkedList<Resource>();
+        toPopulate.add(resource);
+        ResolutionError rethrow = processCandidates(rc, toPopulate, req, candidates);
 
         // Add the dynamic imports candidates.
         // Make sure this is done after the call to processCandidates since we want to ensure
         // fragment candidates are properly hosted before adding the candidates list which makes a copy
-        add(req, candidates);
+        addCandidates(req, candidates);
 
         if (candidates.isEmpty())
         {
@@ -595,25 +477,16 @@
             return rethrow;
         }
 
-        m_populateResultCache.put(resource, Boolean.TRUE);
+        PopulateResult result = new PopulateResult();
+        result.success = true;
+        m_populateResultCache.put(resource, result);
         return null;
     }
 
-    /**
-     * This method performs common processing on the given set of candidates.
-     * Specifically, it removes any candidates which cannot resolve and it
-     * synthesizes candidates for any candidates coming from any attached
-     * fragments, since fragment capabilities only appear once, but technically
-     * each host represents a unique capability.
-     *
-     * @param rc the resolver state.
-     * @param resource the revision being resolved.
-     * @param candidates the candidates to process.
-     * @return a resolve exception to be re-thrown, if any, or null.
-     */
     private ResolutionError processCandidates(
         ResolveContext rc,
-        Resource resource,
+        LinkedList<Resource> toPopulate,
+        Requirement req,
         List<Capability> candidates)
     {
         // Get satisfying candidates and populate their candidates if necessary.
@@ -651,18 +524,25 @@
             // of recursion; thus, any avoided recursion results in fewer
             // exceptions to chain when an error does occur.
             if ((isFragment || !rc.getWirings().containsKey(candCap.getResource()))
-                && !candCap.getResource().equals(resource))
+                && !candCap.getResource().equals(req.getResource()))
             {
-                ResolutionError error = populateResource(rc, candCap.getResource());
-                if (error != null)
+                PopulateResult result = m_populateResultCache.get(candCap.getResource());
+                if (result != null)
                 {
-                    if (rethrow == null)
+                    if (result.error != null)
                     {
-                        rethrow = error;
+                        // Remove the candidate since we weren't able to
+                        // populate its candidates.
+                        itCandCap.remove();
                     }
-                    // Remove the candidate since we weren't able to
-                    // populate its candidates.
-                    itCandCap.remove();
+                    else if (!result.success)
+                    {
+                        toPopulate.add(candCap.getResource());
+                    }
+                }
+                else
+                {
+                    toPopulate.add(candCap.getResource());
                 }
             }
         }
@@ -728,15 +608,14 @@
 
     public boolean isPopulated(Resource resource)
     {
-        Object value = m_populateResultCache.get(resource);
-        return ((value != null) && (value instanceof Boolean));
+        PopulateResult value = m_populateResultCache.get(resource);
+        return (value != null && value.success);
     }
 
     public ResolutionError getResolutionError(Resource resource)
     {
-        Object value = m_populateResultCache.get(resource);
-        return ((value != null) && (value instanceof ResolutionError))
-            ? (ResolutionError) value : null;
+        PopulateResult value = m_populateResultCache.get(resource);
+        return value != null ? value.error : null;
     }
 
     /**
@@ -749,15 +628,14 @@
      * @param req the requirement to add.
      * @param candidates the candidates matching the requirement.
      */
-    private void add(Requirement req, List<Capability> candidates)
+    private void addCandidates(Requirement req, List<Capability> candidates)
     {
-        if (req.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
-        {
-            m_fragmentsPresent = true;
-        }
-
         // Record the candidates.
         m_candidateMap.put(req, new CopyOnWriteList<Capability>(candidates));
+        for (Capability cap : candidates)
+        {
+            m_dependentMap.getOrCompute(cap).add(req);
+        }
     }
 
     /**
@@ -767,11 +645,11 @@
      *
      * @param candidates the bulk requirements and candidates to add.
      */
-    private void add(Map<Requirement, List<Capability>> candidates)
+    private void addCandidates(Map<Requirement, List<Capability>> candidates)
     {
-        for (Entry<Requirement, List<Capability>> entry : candidates.entrySet())
+        for (Map.Entry<Requirement, List<Capability>> entry : candidates.entrySet())
         {
-            add(entry.getKey(), entry.getValue());
+            addCandidates(entry.getKey(), entry.getValue());
         }
     }
 
@@ -855,7 +733,7 @@
      * satisfied by the fragment will end up having the two hosts as potential
      * candidates, rather than the single fragment.
      *
-     * @throws org.osgi.service.resolver.ResolutionException if the removal of any unselected fragments
+     * @return  ResolutionError if the removal of any unselected fragments
      * result in the root module being unable to resolve.
      */
     public ResolutionError prepare(ResolveContext rc)
@@ -864,11 +742,8 @@
         // the fragment map maps a fragment symbolic name to a map that maps
         // a version to a list of fragments requirements matching that symbolic
         // name and version.
-        Map<Capability, Map<String, Map<Version, List<Requirement>>>> hostFragments = Collections.emptyMap();
-        if (m_fragmentsPresent)
-        {
-            hostFragments = populateDependents();
-        }
+        Map<Capability, Map<String, Map<Version, List<Requirement>>>> hostFragments =
+            getHostFragments();
 
         // This method performs the following steps:
         // 1. Select the fragments to attach to a given host.
@@ -1072,7 +947,7 @@
     // the fragment map maps a fragment symbolic name to a map that maps
     // a version to a list of fragments requirements matching that symbolic
     // name and version.
-    private Map<Capability, Map<String, Map<Version, List<Requirement>>>> populateDependents()
+    private Map<Capability, Map<String, Map<Version, List<Requirement>>>> getHostFragments()
     {
         Map<Capability, Map<String, Map<Version, List<Requirement>>>> hostFragments =
             new HashMap<Capability, Map<String, Map<Version, List<Requirement>>>>();
@@ -1082,10 +957,6 @@
             List<Capability> caps = entry.getValue();
             for (Capability cap : caps)
             {
-                // Record the requirement as dependent on the capability.
-                CopyOnWriteSet<Requirement> dependents = m_dependentMap.getOrCompute(cap);
-                dependents.add(req);
-
                 // Keep track of hosts and associated fragments.
                 if (req.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
                 {
@@ -1133,7 +1004,9 @@
     private void removeResource(Resource resource, ResolutionError ex)
     {
         // Add removal reason to result cache.
-        m_populateResultCache.put(resource, ex);
+        PopulateResult result = m_populateResultCache.get(resource);
+        result.success = false;
+        result.error = ex;
         // Remove from dependents.
         Set<Resource> unresolvedResources = new HashSet<Resource>();
         remove(resource, unresolvedResources);
@@ -1214,9 +1087,10 @@
                     m_candidateMap.remove(r);
                     if (!Util.isOptional(r))
                     {
-                        m_populateResultCache.put(
-                            r.getResource(),
-                            new MissingRequirementError(r));
+                        PopulateResult result = m_populateResultCache.get(r.getResource());
+                        result.success = false;
+                        result.error =
+                            new MissingRequirementError(r);
                         unresolvedResources.add(r.getResource());
                     }
                 }
@@ -1238,7 +1112,6 @@
                 m_candidateMap.deepClone(),
                 m_allWrappedHosts,
                 m_populateResultCache,
-                m_fragmentsPresent,
                 m_validOnDemandResources,
                 m_subtitutableMap,
                 m_delta.deepClone());
diff --git a/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java b/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java
index 930e1a5..b73ca51 100644
--- a/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java
+++ b/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java
@@ -23,7 +23,6 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -38,6 +37,7 @@
 
 import org.apache.felix.resolver.util.ArrayMap;
 import org.apache.felix.resolver.util.OpenHashMap;
+
 import org.osgi.framework.namespace.BundleNamespace;
 import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
 import org.osgi.framework.namespace.HostNamespace;
@@ -168,41 +168,32 @@
                 // Create object to hold all candidates.
                 Candidates allCandidates = new Candidates(validOnDemandResources);
 
+                List<Resource> mandatory = new ArrayList<Resource>();
+                List<Resource> toPopulate = new ArrayList<Resource>();
+
                 // Populate mandatory resources; since these are mandatory
                 // resources, failure throws a resolve exception.
-                for (Iterator<Resource> it = mandatoryResources.iterator();
-                    it.hasNext();)
+                for (Resource resource : mandatoryResources)
                 {
-                    Resource resource = it.next();
                     if (Util.isFragment(resource) || (rc.getWirings().get(resource) == null))
                     {
-                        ResolutionError error = allCandidates.populate(rc, resource, Candidates.MANDATORY);
-                        if (error != null)
-                        {
-                            throw error.toException();
-                        }
-                    }
-                    else
-                    {
-                        it.remove();
+                        mandatory.add(resource);
+                        toPopulate.add(resource);
                     }
                 }
-
                 // Populate optional resources; since these are optional
                 // resources, failure does not throw a resolve exception.
                 for (Resource resource : optionalResources)
                 {
-                    boolean isFragment = Util.isFragment(resource);
-                    if (isFragment || (rc.getWirings().get(resource) == null))
+                    if (Util.isFragment(resource) || (rc.getWirings().get(resource) == null))
                     {
-                        ResolutionError error = allCandidates.populate(rc, resource, Candidates.OPTIONAL);
-                        if (error != null)
-                        {
-                            throw error.toException();
-                        }
+                        toPopulate.add(resource);
                     }
                 }
 
+                allCandidates.addMandatoryResources(mandatory);
+                allCandidates.populate(rc, toPopulate);
+
                 // Merge any fragments into hosts.
                 ResolutionError rethrow = allCandidates.prepare(rc);
                 if (rethrow != null)
@@ -226,6 +217,7 @@
 
                 List<Candidates> usesPermutations = session.getUsesPermutations();
                 List<Candidates> importPermutations = session.getImportPermutations();
+                List<Candidates> substPermutations = new ArrayList<Candidates>();
 
                 // Record the initial candidate permutation.
                 usesPermutations.add(allCandidates);
@@ -248,15 +240,23 @@
                 Map<Resource, ResolutionError> faultyResources = null;
                 do
                 {
-                    allCandidates = (usesPermutations.size() > 0)
-                            ? usesPermutations.remove(0)
-                            : (importPermutations.size() > 0
-                                    ? importPermutations.remove(0)
-                                    : null);
-                    if (allCandidates == null)
+                    if (!usesPermutations.isEmpty())
+                    {
+                        allCandidates = usesPermutations.remove(0);
+                    }
+                    else if (!importPermutations.isEmpty())
+                    {
+                        allCandidates = importPermutations.remove(0);
+                    }
+                    else if (!substPermutations.isEmpty())
+                    {
+                        allCandidates = substPermutations.remove(0);
+                    }
+                    else
                     {
                         break;
                     }
+
                     // The delta is used to detect that we have already processed this particular permutation
                     if (!processedDeltas.add(allCandidates.getDelta()))
                     {
@@ -272,7 +272,7 @@
 
 //allCandidates.dump();
 
-                    rethrow = allCandidates.checkSubstitutes(importPermutations);
+                    rethrow = allCandidates.checkSubstitutes( /*substPermutations*/ importPermutations);
                     if (rethrow != null)
                     {
                         continue;
diff --git a/resolver/src/main/java/org/apache/felix/resolver/util/CopyOnWriteList.java b/resolver/src/main/java/org/apache/felix/resolver/util/CopyOnWriteList.java
index 1defe57..4af5dfa 100644
--- a/resolver/src/main/java/org/apache/felix/resolver/util/CopyOnWriteList.java
+++ b/resolver/src/main/java/org/apache/felix/resolver/util/CopyOnWriteList.java
@@ -144,7 +144,28 @@
     }
 
     public boolean containsAll(Collection<?> c) {
-        throw new UnsupportedOperationException();
+        Object[] elements = data;
+        int len = elements.length;
+        for (Object e : c) {
+            if (indexOf(e, elements, len) < 0)
+                return false;
+        }
+        return true;
+    }
+
+    private static int indexOf(Object o, Object[] d, int len) {
+        if (o == null) {
+            for (int i = len; i-- > 0;) {
+                if (d[i] == null)
+                    return i;
+            }
+        } else {
+            for (int i = len; i-- > 0;) {
+                if (o.equals(d[i]))
+                    return i;
+            }
+        }
+        return -1;
     }
 
     public boolean addAll(Collection<? extends E> c) {
@@ -184,20 +205,7 @@
     }
 
     public int indexOf(Object o) {
-        if (o == null) {
-            Object[] d = data;
-            for (int i = d.length; i-- > 0;) {
-                if (d[i] == null)
-                    return i;
-            }
-        } else {
-            Object[] d = data;
-            for (int i = d.length; i-- > 0;) {
-                if (o.equals(d[i]))
-                    return i;
-            }
-        }
-        return -1;
+        return indexOf(o, data, data.length);
     }
 
     public int lastIndexOf(Object o) {
diff --git a/resolver/src/main/java/org/apache/felix/resolver/util/OpenHashMapList.java b/resolver/src/main/java/org/apache/felix/resolver/util/OpenHashMapList.java
index befcd77..f314ed5 100644
--- a/resolver/src/main/java/org/apache/felix/resolver/util/OpenHashMapList.java
+++ b/resolver/src/main/java/org/apache/felix/resolver/util/OpenHashMapList.java
@@ -28,10 +28,7 @@
         super(initialCapacity);
     }
 
-<<<<<<< HEAD
-=======
     @SuppressWarnings("unchecked")
->>>>>>> d6bbce6... Speed up collections a bit
     public OpenHashMapList<K, V> deepClone() {
         OpenHashMapList<K, V> copy = (OpenHashMapList<K, V>) super.clone();
         Object[] values = copy.value;