Need to deal with the fact that hosted fragment capabilities are no longer
wrapped. This impacts indexing of capabilities and also impacts finding
candidates. For the former, we only index fragment capabilities and ignore
them from the host. For the latter, when we come across a candidate from
a fragment we also insert synthesized candidates for any hosts to which
the fragment is attached. (FELIX-2950)


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1148001 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java b/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java
index 261bbea..572ec80 100644
--- a/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java
+++ b/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java
@@ -842,9 +842,6 @@
                                 rte.getMessage(), ex2);
                             throw rte;
                         }
-
-                        // Reindex host with no fragments.
-                        m_resolverState.addRevision(revision);
                     }
 
                     ResolveException re = new ResolveException(
@@ -870,12 +867,9 @@
                 // Update resolver state to remove substituted capabilities.
                 if (!Util.isFragment(revision))
                 {
-                    // Update resolver state by reindexing host with attached
-                    // fragments and removing any substituted exports.
-// TODO: OSGi R4.3 - We could avoid reindexing for fragments if we check it
-//       the revision has fragments or not.
+                    // Reindex the revision's capabilities since its resolved
+                    // capabilities could be different than its declared ones.
                     m_resolverState.addRevision(revision);
-                    m_resolverState.removeSubstitutedCapabilities(revision);
                 }
 
                 // Update the state of the revision's bundle to resolved as well.
@@ -1079,6 +1073,14 @@
 
         synchronized void addRevision(BundleRevision br)
         {
+            // Always attempt to remove the revision, since
+            // this method can be used for re-indexing a revision
+            // after it has been resolved.
+            removeRevision(br);
+
+            // Add the revision and index its declared or resolved
+            // capabilities depending on whether it is resolved or
+            // not.
             m_revisions.add(br);
             List<BundleCapability> caps = (br.getWiring() == null)
                 ? br.getDeclaredCapabilities(null)
@@ -1087,13 +1089,19 @@
             {
                 for (BundleCapability cap : caps)
                 {
-                    CapabilitySet capSet = m_capSets.get(cap.getNamespace());
-                    if (capSet == null)
+                    // If the capability is from a different revision, then
+                    // don't index it since it is a capability from a fragment.
+                    // In that case, the fragment capability is still indexed.
+                    if (cap.getRevision() == br)
                     {
-                        capSet = new CapabilitySet(null, true);
-                        m_capSets.put(cap.getNamespace(), capSet);
+                        CapabilitySet capSet = m_capSets.get(cap.getNamespace());
+                        if (capSet == null)
+                        {
+                            capSet = new CapabilitySet(null, true);
+                            m_capSets.put(cap.getNamespace(), capSet);
+                        }
+                        capSet.addCapability(cap);
                     }
-                    capSet.addCapability(cap);
                 }
             }
 
@@ -1105,25 +1113,27 @@
 
         synchronized void removeRevision(BundleRevision br)
         {
-            m_revisions.remove(br);
-            List<BundleCapability> caps = (br.getWiring() == null)
-                ? br.getDeclaredCapabilities(null)
-                : br.getWiring().getCapabilities(null);
-            if (caps != null)
+            if (m_revisions.remove(br))
             {
-                for (BundleCapability cap : caps)
+                // We only need be concerned with declared capabilities here,
+                // because resolved capabilities will be a subset.
+                List<BundleCapability> caps = br.getDeclaredCapabilities(null);
+                if (caps != null)
                 {
-                    CapabilitySet capSet = m_capSets.get(cap.getNamespace());
-                    if (capSet != null)
+                    for (BundleCapability cap : caps)
                     {
-                        capSet.removeCapability(cap);
+                        CapabilitySet capSet = m_capSets.get(cap.getNamespace());
+                        if (capSet != null)
+                        {
+                            capSet.removeCapability(cap);
+                        }
                     }
                 }
-            }
 
-            if (Util.isFragment(br))
-            {
-                m_fragments.remove(br);
+                if (Util.isFragment(br))
+                {
+                    m_fragments.remove(br);
+                }
             }
         }
 
@@ -1132,36 +1142,6 @@
             return new HashSet(m_fragments);
         }
 
-// TODO: OSGi R4.3 - This will need to be changed once BundleWiring.getCapabilities()
-//       is correctly implemented, since it already has to remove substituted caps.
-        synchronized void removeSubstitutedCapabilities(BundleRevision br)
-        {
-            if (br.getWiring() != null)
-            {
-                // Loop through the revision's package wires and determine if any
-                // of them overlap any of the packages exported by the revision.
-                // If so, then the framework must have chosen to have the revision
-                // import rather than export the package, so we need to remove the
-                // corresponding package capability from the package capability set.
-                for (BundleWire w : br.getWiring().getRequiredWires(null))
-                {
-                    if (w.getCapability().getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
-                    {
-                        for (BundleCapability cap : br.getWiring().getCapabilities(null))
-                        {
-                            if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
-                                && w.getCapability().getAttributes().get(BundleRevision.PACKAGE_NAMESPACE)
-                                    .equals(cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE)))
-                            {
-                                m_capSets.get(BundleRevision.PACKAGE_NAMESPACE).removeCapability(cap);
-                                break;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
         //
         // ResolverState methods.
         //
diff --git a/framework/src/main/java/org/apache/felix/framework/resolver/Candidates.java b/framework/src/main/java/org/apache/felix/framework/resolver/Candidates.java
index c81e286..141b868 100644
--- a/framework/src/main/java/org/apache/felix/framework/resolver/Candidates.java
+++ b/framework/src/main/java/org/apache/felix/framework/resolver/Candidates.java
@@ -33,12 +33,15 @@
 import org.apache.felix.framework.BundleRevisionImpl;
 import org.apache.felix.framework.resolver.Resolver.ResolverState;
 import org.apache.felix.framework.util.Util;
+import org.apache.felix.framework.wiring.BundleCapabilityImpl;
 import org.apache.felix.framework.wiring.BundleRequirementImpl;
 import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
 import org.osgi.framework.wiring.BundleCapability;
 import org.osgi.framework.wiring.BundleRequirement;
 import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
 
 class Candidates
 {
@@ -207,11 +210,22 @@
             ResolveException rethrow = null;
             SortedSet<BundleCapability> candidates =
                 state.getCandidates((BundleRequirementImpl) req, true);
+            Set<BundleCapability> fragmentCands = new HashSet();
             for (Iterator<BundleCapability> itCandCap = candidates.iterator();
                 itCandCap.hasNext(); )
             {
                 BundleCapability candCap = itCandCap.next();
 
+                boolean isFragment = Util.isFragment(candCap.getRevision());
+
+                // If the capability is from a fragment, then record it
+                // because we have to insert associated host capabilities
+                // if the fragment is already attached to any hosts.
+                if (isFragment)
+                {
+                    fragmentCands.add(candCap);
+                }
+
                 // If the candidate revision is a fragment, then always attempt
                 // to populate candidates for its dependency, since it must be
                 // attached to a host to be used. Otherwise, if the candidate
@@ -224,7 +238,7 @@
                 // since we effectively chain exception messages for each level
                 // of recursion; thus, any avoided recursion results in fewer
                 // exceptions to chain when an error does occur.
-                if (Util.isFragment(candCap.getRevision())
+                if (isFragment
                     || ((candCap.getRevision().getWiring() == null)
                         && !candCap.getRevision().equals(revision)))
                 {
@@ -245,6 +259,40 @@
                 }
             }
 
+            // If any of the candidates for the requirement were from a fragment,
+            // then also insert synthesized hosted capabilities for any other host
+            // to which the fragment is attached since they are all effectively
+            // unique capabilities.
+            if (!fragmentCands.isEmpty())
+            {
+                for (BundleCapability fragCand : fragmentCands)
+                {
+                    // Only necessary for resolved fragments.
+                    BundleWiring wiring = fragCand.getRevision().getWiring();
+                    if (wiring != null)
+                    {
+                        // Fragments only have host wire, so each wire represents
+                        // an attached host.
+                        for (BundleWire wire : wiring.getRequiredWires(null))
+                        {
+                            // If the capability is a package, then make sure the
+                            // host actually provides it in its resolved capabilities,
+                            // since it may be a substitutable export.
+                            if (!fragCand.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
+                                || wire.getProviderWiring().getCapabilities(null).contains(fragCand))
+                            {
+                                // Note that we can just add this as a candidate
+                                // directly, since we know it is already resolved.
+                                candidates.add(
+                                    new HostedCapability(
+                                        wire.getCapability().getRevision(),
+                                        (BundleCapabilityImpl) fragCand));
+                            }
+                        }
+                    }
+                }
+            }
+
             // If there are no candidates for the current requirement
             // and it is not optional, then create, cache, and throw
             // a resolve exception.
@@ -296,6 +344,20 @@
     public final boolean populate(
         ResolverState state, BundleRevision revision, boolean isGreedyAttach)
     {
+        // Get the current result cache value, to make sure the revision
+        // hasn't already been populated.
+        Object cacheValue = m_populateResultCache.get(revision);
+        // Has been unsuccessfully populated.
+        if (cacheValue instanceof ResolveException)
+        {
+            return false;
+        }
+        // Has been successfully populated.
+        else if (cacheValue instanceof Boolean)
+        {
+            return true;
+        }
+
         // We will always attempt to populate fragments, since this is necessary
         // for greedy attaching of fragment. However, we'll only attempt to
         // populate optional non-fragment revisions if they aren't already
@@ -316,76 +378,70 @@
             // lookup again in populate().
             if (isGreedyAttach && isFragment)
             {
-                // Get the current result cache value, to make sure the revision
-                // hasn't already been populated.
-                Object cacheValue = m_populateResultCache.get(revision);
-                if (cacheValue == null)
+                // Create a modifiable list of the revision's requirements.
+                List<BundleRequirement> remainingReqs =
+                    new ArrayList(revision.getDeclaredRequirements(null));
+
+                // Find the host requirement.
+                BundleRequirement hostReq = null;
+                for (Iterator<BundleRequirement> it = remainingReqs.iterator();
+                    it.hasNext(); )
                 {
-                    // Create a modifiable list of the revision's requirements.
-                    List<BundleRequirement> remainingReqs =
-                        new ArrayList(revision.getDeclaredRequirements(null));
-
-                    // Find the host requirement.
-                    BundleRequirement hostReq = null;
-                    for (Iterator<BundleRequirement> it = remainingReqs.iterator();
-                        it.hasNext(); )
+                    BundleRequirement r = it.next();
+                    if (r.getNamespace().equals(BundleRevision.HOST_NAMESPACE))
                     {
-                        BundleRequirement r = it.next();
-                        if (r.getNamespace().equals(BundleRevision.HOST_NAMESPACE))
-                        {
-                            hostReq = r;
-                            it.remove();
-                            break;
-                        }
+                        hostReq = r;
+                        it.remove();
+                        break;
                     }
-
-                    // Get candidates hosts and keep any that have been populated.
-                    SortedSet<BundleCapability> hosts =
-                        state.getCandidates((BundleRequirementImpl) hostReq, false);
-                    for (Iterator<BundleCapability> it = hosts.iterator(); it.hasNext(); )
-                    {
-                        BundleCapability host = it.next();
-                        if (!isPopulated(host.getRevision()))
-                        {
-                            it.remove();
-                        }
-                    }
-
-                    // If there aren't any populated hosts, then we can just
-                    // return since this fragment isn't needed.
-                    if (hosts.isEmpty())
-                    {
-                        return false;
-                    }
-
-                    // If there are populates host candidates, then finish up
-                    // some other checks and prepopulate the result cache with
-                    // the work we've done so far.
-
-                    // Verify that any required execution environment is satisfied.
-                    state.checkExecutionEnvironment(revision);
-
-                    // Verify that any native libraries match the current platform.
-                    state.checkNativeLibraries(revision);
-
-                    // Record cycle count, but start at -1 since it will
-                    // be incremented again in populate().
-                    Integer cycleCount = new Integer(-1);
-
-                    // Create a local map for populating candidates first, just in case
-                    // the revision is not resolvable.
-                    Map<BundleRequirement, SortedSet<BundleCapability>> localCandidateMap =
-                        new HashMap<BundleRequirement, SortedSet<BundleCapability>>();
-
-                    // Add the discovered host candidates to the local candidate map.
-                    localCandidateMap.put(hostReq, hosts);
-
-                    // 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(revision,
-                        new Object[] { cycleCount, localCandidateMap, remainingReqs });
                 }
+
+                // Get candidates hosts and keep any that have been populated.
+                SortedSet<BundleCapability> hosts =
+                    state.getCandidates((BundleRequirementImpl) hostReq, false);
+                for (Iterator<BundleCapability> it = hosts.iterator(); it.hasNext(); )
+                {
+                    BundleCapability host = it.next();
+                    if (!isPopulated(host.getRevision()))
+                    {
+                        it.remove();
+                    }
+                }
+
+                // If there aren't any populated hosts, then we can just
+                // return since this fragment isn't needed.
+                if (hosts.isEmpty())
+                {
+                    return false;
+                }
+
+                // If there are populates host candidates, then finish up
+                // some other checks and prepopulate the result cache with
+                // the work we've done so far.
+
+                // Verify that any required execution environment is satisfied.
+                state.checkExecutionEnvironment(revision);
+
+                // Verify that any native libraries match the current platform.
+                state.checkNativeLibraries(revision);
+
+                // Record cycle count, but start at -1 since it will
+                // be incremented again in populate().
+                Integer cycleCount = new Integer(-1);
+
+                // Create a local map for populating candidates first, just in case
+                // the revision is not resolvable.
+                Map<BundleRequirement, SortedSet<BundleCapability>> localCandidateMap =
+                    new HashMap<BundleRequirement, SortedSet<BundleCapability>>();
+
+                // Add the discovered host candidates to the local candidate map.
+                localCandidateMap.put(hostReq, hosts);
+
+                // 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(revision,
+                    new Object[] { cycleCount, localCandidateMap, remainingReqs });
             }
 
             // Try to populate candidates for the optional revision.