The case of dynamically importing from a fragment was not correctly
handled when we previously refactored the code so that we don't
synthesize capabilities for attached fragments on the host. I refactored
this code to avoid duplication and to ensure that the normal path
and the dynamic import path are resolved the same way. (FELIX-2950)


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1172688 13f79535-47bb-0310-9956-ffa450edef68
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 f26181b..1bf0512 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
@@ -160,7 +160,7 @@
         // However, for on-demand fragments only populate if their host
         // is already populated.
         if ((resolution != ON_DEMAND)
-            || (isFragment && populateFragment(state, revision)))
+            || (isFragment && populateFragmentOndemand(state, revision)))
         {
             if (resolution == MANDATORY)
             {
@@ -284,96 +284,11 @@
                 continue;
             }
 
-            // Get satisfying candidates and populate their candidates if necessary.
-            ResolveException rethrow = null;
+            // Process the candidates, removing any candidates that
+            // cannot resolve.
             SortedSet<BundleCapability> candidates =
                 state.getCandidates((BundleRequirementImpl) req, true);
-            Set<BundleCapability> fragmentCands = null;
-            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)
-                {
-                    if (fragmentCands == null)
-                    {
-                        fragmentCands = new HashSet<BundleCapability>();
-                    }
-                    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
-                // revision is not already resolved and is not the current version
-                // we are trying to populate, then populate the candidates for
-                // its dependencies as well.
-                // NOTE: Technically, we don't have to check to see if the
-                // candidate revision is equal to the current revision, but this
-                // saves us from recursing and also simplifies exceptions messages
-                // 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 (isFragment
-                    || ((candCap.getRevision().getWiring() == null)
-                        && !candCap.getRevision().equals(revision)))
-                {
-                    try
-                    {
-                        populateRevision(state, candCap.getRevision());
-                    }
-                    catch (ResolveException ex)
-                    {
-                        if (rethrow == null)
-                        {
-                            rethrow = ex;
-                        }
-                        // Remove the candidate since we weren't able to
-                        // populate its candidates.
-                        itCandCap.remove();
-                    }
-                }
-            }
-
-            // 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 != null)
-            {
-                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));
-                            }
-                        }
-                    }
-                }
-            }
+            ResolveException rethrow = processCandidates(state, revision, candidates);
 
             // If there are no candidates for the current requirement
             // and it is not optional, then create, cache, and throw
@@ -420,7 +335,7 @@
         }
     }
 
-    private boolean populateFragment(ResolverState state, BundleRevision revision)
+    private boolean populateFragmentOndemand(ResolverState state, BundleRevision revision)
         throws ResolveException
     {
         // Create a modifiable list of the revision's requirements.
@@ -480,19 +395,6 @@
         return true;
     }
 
-    public boolean isPopulated(BundleRevision revision)
-    {
-        Object value = m_populateResultCache.get(revision);
-        return ((value != null) && (value instanceof Boolean));
-    }
-
-    public ResolveException getResolveException(BundleRevision revision)
-    {
-        Object value = m_populateResultCache.get(revision);
-        return ((value != null) && (value instanceof ResolveException))
-            ? (ResolveException) value : null;
-    }
-
     public void populateDynamic(
         ResolverState state, BundleRevision revision,
         BundleRequirement req, SortedSet<BundleCapability> candidates)
@@ -504,28 +406,9 @@
         // Add the dynamic imports candidates.
         add(req, candidates);
 
-        // Populate the candidates for the dynamic import.
-        ResolveException rethrow = null;
-        for (Iterator<BundleCapability> itCandCap = candidates.iterator();
-            itCandCap.hasNext(); )
-        {
-            BundleCapability candCap = itCandCap.next();
-            if (candCap.getRevision().getWiring() == null)
-            {
-                try
-                {
-                    populateRevision(state, candCap.getRevision());
-                }
-                catch (ResolveException ex)
-                {
-                    if (rethrow == null)
-                    {
-                        rethrow = ex;
-                    }
-                    itCandCap.remove();
-                }
-            }
-        }
+        // Process the candidates, removing any candidates that
+        // cannot resolve.
+        ResolveException rethrow = processCandidates(state, revision, candidates);
 
         if (candidates.isEmpty())
         {
@@ -540,6 +423,127 @@
     }
 
     /**
+     * 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 state the resolver state.
+     * @param revision the revision being resolved.
+     * @param candidates the candidates to process.
+     * @return a resolve exception to be re-thrown, if any, or null.
+     */
+    private ResolveException processCandidates(
+        ResolverState state,
+        BundleRevision revision,
+        SortedSet<BundleCapability> candidates)
+    {
+        // Get satisfying candidates and populate their candidates if necessary.
+        ResolveException rethrow = null;
+        Set<BundleCapability> fragmentCands = null;
+        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)
+            {
+                if (fragmentCands == null)
+                {
+                    fragmentCands = new HashSet<BundleCapability>();
+                }
+                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
+            // revision is not already resolved and is not the current version
+            // we are trying to populate, then populate the candidates for
+            // its dependencies as well.
+            // NOTE: Technically, we don't have to check to see if the
+            // candidate revision is equal to the current revision, but this
+            // saves us from recursing and also simplifies exceptions messages
+            // 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 (isFragment
+                || ((candCap.getRevision().getWiring() == null)
+                    && !candCap.getRevision().equals(revision)))
+            {
+                try
+                {
+                    populateRevision(state, candCap.getRevision());
+                }
+                catch (ResolveException ex)
+                {
+                    if (rethrow == null)
+                    {
+                        rethrow = ex;
+                    }
+                    // Remove the candidate since we weren't able to
+                    // populate its candidates.
+                    itCandCap.remove();
+                }
+            }
+        }
+
+        // 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 != null)
+        {
+            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));
+                        }
+                    }
+                }
+            }
+        }
+
+        return rethrow;
+    }
+
+    public boolean isPopulated(BundleRevision revision)
+    {
+        Object value = m_populateResultCache.get(revision);
+        return ((value != null) && (value instanceof Boolean));
+    }
+
+    public ResolveException getResolveException(BundleRevision revision)
+    {
+        Object value = m_populateResultCache.get(revision);
+        return ((value != null) && (value instanceof ResolveException))
+            ? (ResolveException) value : null;
+    }
+
+    /**
      * Adds a requirement and its matching candidates to the internal data
      * structure. This method assumes it owns the data being passed in and
      * does not make a copy. It takes the data and processes, such as calculating
@@ -614,37 +618,11 @@
     **/
     public void prepare()
     {
-        boolean init = false;
-
         if (m_fragmentsPresent)
         {
             populateDependents();
-            init = true;
         }
 
-        // If the root is a singleton, then prefer it over any other singleton.
-// TODO: OSGi R4.3/SINGLETON - How do we prefer the root as a singleton?
-/*
-        if ((m_root != null) && isSingleton(m_root))
-        {
-            BundleRevision singleton = singletons.get(m_root.getSymbolicName());
-            singletons.put(m_root.getSymbolicName(), m_root);
-            if ((singleton != null) && !singleton.equals(m_root))
-            {
-                if (singleton.getWiring() != null)
-                {
-                    throw new ResolveException(
-                        "Cannot resolve singleton "
-                        + m_root
-                        + " because "
-                        + singleton
-                        + " singleton is already resolved.",
-                        m_root, null);
-                }
-                removeRevision(singleton);
-            }
-        }
-*/
         // This method performs the following steps:
         // 1. Select the fragments to attach to a given host.
         // 2. Wrap hosts and attach fragments.