Move singleton selection back into the resolver state, in preparation
for implementing resolver hook singleton selection. (FELIX-2986)


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1165690 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 9c8daa1..deb49e6 100644
--- a/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java
+++ b/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java
@@ -50,6 +50,7 @@
 import org.osgi.framework.Constants;
 import org.osgi.framework.PackagePermission;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
 import org.osgi.framework.hooks.resolver.ResolverHook;
 import org.osgi.framework.hooks.resolver.ResolverHookFactory;
 import org.osgi.framework.wiring.BundleCapability;
@@ -126,19 +127,29 @@
             // Extensions are resolved differently.
             for (Iterator<BundleRevision> it = mandatoryRevisions.iterator(); it.hasNext(); )
             {
-                BundleImpl bundle = (BundleImpl) it.next().getBundle();
+                BundleRevision br = it.next();
+                BundleImpl bundle = (BundleImpl) br.getBundle();
                 if (bundle.isExtension())
                 {
                     it.remove();
                 }
+                else if (Util.isSingleton(br) && !m_resolverState.isSelectedSingleton(br))
+                {
+                    throw new ResolveException("Singleton conflict.", br, null);
+                }
             }
             for (Iterator<BundleRevision> it = optionalRevisions.iterator(); it.hasNext(); )
             {
-                BundleImpl bundle = (BundleImpl) it.next().getBundle();
+                BundleRevision br = it.next();
+                BundleImpl bundle = (BundleImpl) br.getBundle();
                 if (bundle.isExtension())
                 {
                     it.remove();
                 }
+                else if (Util.isSingleton(br) && !m_resolverState.isSelectedSingleton(br))
+                {
+                    it.remove();
+                }
             }
 
             // Get resolver hook factories.
@@ -918,6 +929,10 @@
         private final Set<BundleRevision> m_fragments;
         // Capability sets.
         private final Map<String, CapabilitySet> m_capSets;
+        // Maps singleton symbolic names to list of bundle revisions sorted by version.
+        private final Map<String, List<BundleRevision>> m_singletons;
+        // Maps singleton symbolic names to selected bundle revision.
+        private final Map<String, BundleRevision> m_selectedSingleton;
         // Execution environment.
         private final String m_fwkExecEnvStr;
         // Parsed framework environments
@@ -938,6 +953,8 @@
             m_revisions = new HashSet<BundleRevision>();
             m_fragments = new HashSet<BundleRevision>();
             m_capSets = new HashMap<String, CapabilitySet>();
+            m_singletons = new HashMap<String, List<BundleRevision>>();
+            m_selectedSingleton = new HashMap<String, BundleRevision>();
 
             m_fwkExecEnvStr = (fwkExecEnvStr != null) ? fwkExecEnvStr.trim() : null;
             m_fwkExecEnvSet = parseExecutionEnvironments(fwkExecEnvStr);
@@ -975,10 +992,52 @@
             // 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);
+            if (Util.isSingleton(br))
+            {
+                // Index the new singleton.
+                indexSingleton(m_singletons, br);
+                // Get the currently selected singleton.
+                BundleRevision selected = m_selectedSingleton.get(br.getSymbolicName());
+                // Get the highest singleton version.
+                BundleRevision highest = m_singletons.get(br.getSymbolicName()).get(0);
+                // Select the highest version if not already selected or resolved.
+                if ((selected == null)
+                    || ((selected.getWiring() == null) && (selected != highest)))
+                {
+                    m_selectedSingleton.put(br.getSymbolicName(), highest);
+                    if (selected != null)
+                    {
+                        deindexCapabilities(selected);
+                        m_revisions.remove(selected);
+                        if (Util.isFragment(selected))
+                        {
+                            m_fragments.remove(selected);
+                        }
+                    }
+                    br = highest;
+                }
+                else if (selected != null)
+                {
+                    // Since the newly added singleton was not selected, null
+                    // it out so that it is ignored.
+                    br = null;
+                }
+            }
+
+            // Add the revision and index its capabilities.
+            if (br != null)
+            {
+                m_revisions.add(br);
+                if (Util.isFragment(br))
+                {
+                    m_fragments.add(br);
+                }
+                indexCapabilities(br);
+            }
+        }
+
+        private synchronized void indexCapabilities(BundleRevision br)
+        {
             List<BundleCapability> caps = (br.getWiring() == null)
                 ? br.getDeclaredCapabilities(null)
                 : br.getWiring().getCapabilities(null);
@@ -989,6 +1048,8 @@
                     // 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.
+                    // It will be the resolver's responsibility to find all
+                    // attached hosts for fragments.
                     if (cap.getRevision() == br)
                     {
                         CapabilitySet capSet = m_capSets.get(cap.getNamespace());
@@ -1001,10 +1062,24 @@
                     }
                 }
             }
+        }
 
-            if (Util.isFragment(br))
+        private synchronized void deindexCapabilities(BundleRevision br)
+        {
+            // We only need be concerned with declared capabilities here,
+            // because resolved capabilities will be a subset, since fragment
+            // capabilities are not considered to be part of the host.
+            List<BundleCapability> caps = br.getDeclaredCapabilities(null);
+            if (caps != null)
             {
-                m_fragments.add(br);
+                for (BundleCapability cap : caps)
+                {
+                    CapabilitySet capSet = m_capSets.get(cap.getNamespace());
+                    if (capSet != null)
+                    {
+                        capSet.removeCapability(cap);
+                    }
+                }
             }
         }
 
@@ -1012,25 +1087,45 @@
         {
             if (m_revisions.remove(br))
             {
-                // 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)
-                {
-                    for (BundleCapability cap : caps)
-                    {
-                        CapabilitySet capSet = m_capSets.get(cap.getNamespace());
-                        if (capSet != null)
-                        {
-                            capSet.removeCapability(cap);
-                        }
-                    }
-                }
+                deindexCapabilities(br);
 
                 if (Util.isFragment(br))
                 {
                     m_fragments.remove(br);
                 }
+
+                // If this module is a singleton, then remove it from the
+                // singleton map and potentially select a new singleton.
+                List<BundleRevision> revisions = m_singletons.get(br.getSymbolicName());
+                if (revisions != null)
+                {
+                    BundleRevision selected = m_selectedSingleton.get(br.getSymbolicName());
+                    revisions.remove(br);
+                    if (revisions.isEmpty())
+                    {
+                        m_singletons.remove(br.getSymbolicName());
+                    }
+
+                    // If this was the selected singleton, then we have to
+                    // select another.
+                    if (selected == br)
+                    {
+                        if (!revisions.isEmpty())
+                        {
+                            selected = revisions.get(0);
+                            m_selectedSingleton.put(br.getSymbolicName(), selected);
+                            if (Util.isFragment(selected))
+                            {
+                                m_fragments.add(selected);
+                            }
+                            indexCapabilities(selected);
+                        }
+                        else
+                        {
+                            m_selectedSingleton.remove(br.getSymbolicName());
+                        }
+                    }
+                }
             }
         }
 
@@ -1039,6 +1134,11 @@
             return new HashSet(m_fragments);
         }
 
+        synchronized boolean isSelectedSingleton(BundleRevision br)
+        {
+            return (m_selectedSingleton.get(br.getSymbolicName()) == br);
+        }
+
         //
         // ResolverState methods.
         //
@@ -1267,4 +1367,62 @@
         }
         return newSet;
     }
+
+    private static void indexSingleton(
+        Map<String, List<BundleRevision>> singletons, BundleRevision br)
+    {
+        List<BundleRevision> revisions = singletons.get(br.getSymbolicName());
+
+        // We want to add the fragment into the list of matching
+        // fragments in sorted order (descending version and
+        // ascending bundle identifier). Insert using a simple
+        // binary search algorithm.
+        if (revisions == null)
+        {
+            revisions = new ArrayList<BundleRevision>();
+            revisions.add(br);
+        }
+        else
+        {
+            Version version = br.getVersion();
+            int top = 0, bottom = revisions.size() - 1;
+            while (top <= bottom)
+            {
+                int middle = (bottom - top) / 2 + top;
+                Version middleVersion = revisions.get(middle).getVersion();
+                // Sort in reverse version order.
+                int cmp = middleVersion.compareTo(version);
+                if (cmp < 0)
+                {
+                    bottom = middle - 1;
+                }
+                else if (cmp == 0)
+                {
+                    // Sort further by ascending bundle ID.
+                    long middleId = revisions.get(middle).getBundle().getBundleId();
+                    long exportId = br.getBundle().getBundleId();
+                    if (middleId < exportId)
+                    {
+                        top = middle + 1;
+                    }
+                    else
+                    {
+                        bottom = middle - 1;
+                    }
+                }
+                else
+                {
+                    top = middle + 1;
+                }
+            }
+
+            // Ignore duplicates.
+            if ((top >= revisions.size()) || (revisions.get(top) != br))
+            {
+                revisions.add(top, br);
+            }
+        }
+
+        singletons.put(br.getSymbolicName(), revisions);
+    }
 }
\ No newline at end of file
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 4e4ccda..f26181b 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
@@ -612,7 +612,7 @@
      * @throws ResolveException if the removal of any unselected fragments result
      *         in the root module being unable to resolve.
     **/
-    public void prepare(List<BundleRevision> existingSingletons)
+    public void prepare()
     {
         boolean init = false;
 
@@ -622,51 +622,6 @@
             init = true;
         }
 
-        final Map<String, BundleRevision> singletons = new HashMap<String, BundleRevision>();
-
-        for (Iterator<BundleRevision> it = m_involvedRevisions.iterator(); it.hasNext(); )
-        {
-            BundleRevision br = it.next();
-            if (isSingleton(br))
-            {
-                if (!init)
-                {
-                    populateDependents();
-                    init = true;
-                }
-
-                // See if there is an existing singleton for the
-                // revision's symbolic name.
-                BundleRevision singleton = singletons.get(br.getSymbolicName());
-                // If there is no existing singleton or this revision is
-                // a resolved singleton or this revision has a higher version
-                // and the existing singleton is not resolved, then select
-                // this revision as the singleton.
-                if ((singleton == null)
-                    || (br.getWiring() != null)
-                    || ((br.getVersion().compareTo(singleton.getVersion()) > 0)
-                        && (singleton.getWiring() == null)))
-                {
-                    singletons.put(br.getSymbolicName(), br);
-                    // Remove the singleton revision from the candidates
-                    // if it wasn't selected.
-                    if (singleton != null)
-                    {
-                        removeRevision(
-                            singleton,
-                            new ResolveException(
-                                "Conflict with another singleton.", singleton, null));
-                    }
-                }
-                else
-                {
-                    removeRevision(br,
-                        new ResolveException(
-                            "Conflict with another singleton.", br, null));
-                }
-            }
-        }
-
         // 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?
 /*
@@ -690,22 +645,6 @@
             }
         }
 */
-
-        // Make sure selected singletons do not conflict with existing
-        // singletons passed into this method.
-        for (int i = 0; (existingSingletons != null) && (i < existingSingletons.size()); i++)
-        {
-            BundleRevision existing = existingSingletons.get(i);
-            BundleRevision singleton = singletons.get(existing.getSymbolicName());
-            if ((singleton != null) && (singleton != existing))
-            {
-                singletons.remove(singleton.getSymbolicName());
-                removeRevision(singleton,
-                    new ResolveException(
-                        "Conflict with another singleton.", singleton, null));
-            }
-        }
-
         // This method performs the following steps:
         // 1. Select the fragments to attach to a given host.
         // 2. Wrap hosts and attach fragments.
diff --git a/framework/src/main/java/org/apache/felix/framework/resolver/ResolverImpl.java b/framework/src/main/java/org/apache/felix/framework/resolver/ResolverImpl.java
index 7d561da..2588613 100644
--- a/framework/src/main/java/org/apache/felix/framework/resolver/ResolverImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/resolver/ResolverImpl.java
@@ -117,7 +117,7 @@
                 }
 
                 // Merge any fragments into hosts.
-                allCandidates.prepare(getResolvedSingletons(state));
+                allCandidates.prepare();
 
                 // Create a combined list of populated revisions; for
                 // optional revisions. We do not need to consider ondemand
@@ -303,7 +303,7 @@
                     }
 
                     // Merge any fragments into hosts.
-                    allCandidates.prepare(getResolvedSingletons(state));
+                    allCandidates.prepare();
 
                     // Record the initial candidate permutation.
                     m_usesPermutations.add(allCandidates);
@@ -393,25 +393,6 @@
         return null;
     }
 
-    private static List<BundleRevision> getResolvedSingletons(ResolverState state)
-    {
-        BundleRequirementImpl req = new BundleRequirementImpl(
-            null,
-            BundleCapabilityImpl.SINGLETON_NAMESPACE,
-            Collections.EMPTY_MAP,
-            Collections.EMPTY_MAP);
-        SortedSet<BundleCapability> caps = state.getCandidates(req, true);
-        List<BundleRevision> singletons = new ArrayList();
-        for (BundleCapability cap : caps)
-        {
-            if (cap.getRevision().getWiring() != null)
-            {
-                singletons.add(cap.getRevision());
-            }
-        }
-        return singletons;
-    }
-
     private static Candidates getDynamicImportCandidates(
         ResolverState state, BundleRevision revision, String pkgName)
     {
diff --git a/framework/src/main/java/org/apache/felix/framework/util/Util.java b/framework/src/main/java/org/apache/felix/framework/util/Util.java
index f9a0719..41e4305 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/Util.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/Util.java
@@ -26,6 +26,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Properties;
 import org.apache.felix.framework.Logger;
 import org.apache.felix.framework.capabilityset.CapabilitySet;
@@ -610,6 +611,35 @@
     }
 
     /**
+     * Returns true if the specified bundle revision is a singleton
+     * (i.e., directive singleton:=true in Bundle-SymbolicName).
+     *
+     * @param revision the revision to check for singleton status.
+     * @return true if the revision is a singleton, false otherwise.
+    **/
+    public static boolean isSingleton(BundleRevision revision)
+    {
+        final List<BundleCapability> caps = revision.getDeclaredCapabilities(null);
+        for (BundleCapability cap : caps)
+        {
+            // Find the bundle capability and check its directives.
+            if (cap.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE))
+            {
+                for (Entry<String, String> entry : cap.getDirectives().entrySet())
+                {
+                    if (entry.getKey().equalsIgnoreCase(Constants.SINGLETON_DIRECTIVE))
+                    {
+                        return Boolean.valueOf((String) entry.getValue());
+                    }
+                }
+                // Can only have one bundle capability, so break.
+                break;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Checks if the provided module definition declares a fragment host.
      *
      * @param module the module to check
diff --git a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
index e0639cb..4ebd09f 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
@@ -131,26 +131,6 @@
                         hostAttrs));
                 }
             }
-
-            // Add a singleton capability if the bundle is a singleton.
-            // This is sort of a hack, but we need this for the resolver
-            // to be able to resolve singletons. It is not possible to
-            // attach this information to the bundle or host capabilities
-            // because fragments don't have those capabilities, but fragments
-            // can be singletons too.
-// TODO: OSGi R4.4 - Eventually we will have an identity capability from OBR
-//       that we can use instead of this custom singleton capability.
-            if (isSingleton(bundleCap))
-            {
-                Map<String, Object> singletonAttrs =
-                    new HashMap<String, Object>(bundleCap.getAttributes());
-                Object value = singletonAttrs.remove(BundleRevision.BUNDLE_NAMESPACE);
-                singletonAttrs.put(BundleCapabilityImpl.SINGLETON_NAMESPACE, value);
-                capList.add(new BundleCapabilityImpl(
-                    owner, BundleCapabilityImpl.SINGLETON_NAMESPACE,
-                    Collections.EMPTY_MAP,
-                    singletonAttrs));
-            }
         }
 
         // Verify that bundle symbolic name is specified.
@@ -288,19 +268,6 @@
         m_isExtension = checkExtensionBundle(headerMap);
     }
 
-    private static boolean isSingleton(BundleCapabilityImpl cap)
-    {
-        if (cap.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE))
-        {
-            String value = cap.getDirectives().get(Constants.SINGLETON_DIRECTIVE);
-            if ((value != null) && Boolean.valueOf(value))
-            {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private static List<ParsedHeaderClause> normalizeImportClauses(
         Logger logger, List<ParsedHeaderClause> clauses, String mv)
         throws BundleException
diff --git a/framework/src/main/java/org/apache/felix/framework/wiring/BundleCapabilityImpl.java b/framework/src/main/java/org/apache/felix/framework/wiring/BundleCapabilityImpl.java
index 4114ab0..ba36535 100644
--- a/framework/src/main/java/org/apache/felix/framework/wiring/BundleCapabilityImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/wiring/BundleCapabilityImpl.java
@@ -34,8 +34,6 @@
 
 public class BundleCapabilityImpl implements BundleCapability
 {
-    public static final String SINGLETON_NAMESPACE = "singleton";
-
     public static final String VERSION_ATTR = "version";
 
     private final BundleRevision m_revision;