Move singleton handling into the resolver. (FELIX-2859)


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1078140 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/Felix.java b/framework/src/main/java/org/apache/felix/framework/Felix.java
index 542a0fa..6d832dc 100644
--- a/framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -3126,7 +3126,7 @@
         List<Attribute> attrs = new ArrayList<Attribute>(1);
         attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
         Requirement req = new RequirementImpl(null, Capability.PACKAGE_NAMESPACE, dirs, attrs);
-        Set<Capability> exports = m_resolver.getCandidates(null, req, false);
+        Set<Capability> exports = m_resolver.getCandidates(req, false);
 
         // We only want resolved capabilities.
         for (Iterator<Capability> it = exports.iterator(); it.hasNext(); )
@@ -3273,7 +3273,7 @@
                         attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
                         Requirement req =
                             new RequirementImpl(null, Capability.PACKAGE_NAMESPACE, dirs, attrs);
-                        Set<Capability> exports = m_resolver.getCandidates(null, req, false);
+                        Set<Capability> exports = m_resolver.getCandidates(req, false);
                         // We only want resolved capabilities.
                         for (Iterator<Capability> it = exports.iterator(); it.hasNext(); )
                         {
@@ -3986,10 +3986,9 @@
             m_resolverState.removeModule(m);
         }
 
-        Set<Capability> getCandidates(
-            Module reqModule, Requirement req, boolean obeyMandatory)
+        Set<Capability> getCandidates(Requirement req, boolean obeyMandatory)
         {
-            return m_resolverState.getCandidates(reqModule, req, obeyMandatory);
+            return m_resolverState.getCandidates(req, obeyMandatory);
         }
 
         void resolve(Module rootModule) throws ResolveException
@@ -4160,7 +4159,7 @@
             attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
             Requirement req = new RequirementImpl(
                 module, Capability.PACKAGE_NAMESPACE, dirs, attrs);
-            Set<Capability> candidates = m_resolverState.getCandidates(module, req, false);
+            Set<Capability> candidates = m_resolverState.getCandidates(req, false);
 
             return !candidates.isEmpty();
         }
diff --git a/framework/src/main/java/org/apache/felix/framework/ModuleImpl.java b/framework/src/main/java/org/apache/felix/framework/ModuleImpl.java
index d94b8ed..6b727f0 100644
--- a/framework/src/main/java/org/apache/felix/framework/ModuleImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/ModuleImpl.java
@@ -2297,7 +2297,7 @@
             attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
             Requirement req = new RequirementImpl(
                 module, Capability.PACKAGE_NAMESPACE, dirs, attrs);
-            Set<Capability> exporters = resolver.getCandidates(module, req, false);
+            Set<Capability> exporters = resolver.getCandidates(req, false);
 
             Wire wire = null;
             try
@@ -2336,7 +2336,7 @@
         attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
         Requirement req = new RequirementImpl(
             module, Capability.PACKAGE_NAMESPACE, dirs, attrs);
-        Set<Capability> exports = resolver.getCandidates(module, req, false);
+        Set<Capability> exports = resolver.getCandidates(req, false);
         if (exports.size() > 0)
         {
             boolean classpath = false;
diff --git a/framework/src/main/java/org/apache/felix/framework/ResolverStateImpl.java b/framework/src/main/java/org/apache/felix/framework/ResolverStateImpl.java
index 7143fb6..a81d33d 100644
--- a/framework/src/main/java/org/apache/felix/framework/ResolverStateImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/ResolverStateImpl.java
@@ -167,8 +167,9 @@
     //
 
     public synchronized SortedSet<Capability> getCandidates(
-        Module module, Requirement req, boolean obeyMandatory)
+        Requirement req, boolean obeyMandatory)
     {
+        Module module = req.getModule();
         SortedSet<Capability> result = new TreeSet<Capability>(new CandidateComparator());
 
         CapabilitySet capSet = m_capSets.get(req.getNamespace());
diff --git a/framework/src/main/java/org/apache/felix/framework/capabilityset/Capability.java b/framework/src/main/java/org/apache/felix/framework/capabilityset/Capability.java
index 7bb1534..2a77b6a 100644
--- a/framework/src/main/java/org/apache/felix/framework/capabilityset/Capability.java
+++ b/framework/src/main/java/org/apache/felix/framework/capabilityset/Capability.java
@@ -26,6 +26,7 @@
     static final String MODULE_NAMESPACE = "module";
     static final String HOST_NAMESPACE = "host";
     static final String PACKAGE_NAMESPACE = "package";
+    static final String SINGLETON_NAMESPACE = "singleton";
 
     public static final String PACKAGE_ATTR = "package";
     public static final String VERSION_ATTR = "version";
diff --git a/framework/src/main/java/org/apache/felix/framework/capabilityset/CapabilitySet.java b/framework/src/main/java/org/apache/felix/framework/capabilityset/CapabilitySet.java
index c8957f9..cd1990a 100644
--- a/framework/src/main/java/org/apache/felix/framework/capabilityset/CapabilitySet.java
+++ b/framework/src/main/java/org/apache/felix/framework/capabilityset/CapabilitySet.java
@@ -156,7 +156,11 @@
     {
         Set<Capability> matches = new HashSet<Capability>();
 
-        if (sf.getOperation() == SimpleFilter.AND)
+        if (sf.getOperation() == SimpleFilter.MATCH_ALL)
+        {
+            matches.addAll(caps);
+        }
+        else if (sf.getOperation() == SimpleFilter.AND)
         {
             // Evaluate each subfilter against the remaining capabilities.
             // For AND we calculate the intersection of each subfilter.
diff --git a/framework/src/main/java/org/apache/felix/framework/capabilityset/SimpleFilter.java b/framework/src/main/java/org/apache/felix/framework/capabilityset/SimpleFilter.java
index 68815c2..822d9d0 100644
--- a/framework/src/main/java/org/apache/felix/framework/capabilityset/SimpleFilter.java
+++ b/framework/src/main/java/org/apache/felix/framework/capabilityset/SimpleFilter.java
@@ -23,6 +23,7 @@
 
 public class SimpleFilter
 {
+    public static final int MATCH_ALL = 0;
     public static final int AND = 1;
     public static final int OR = 2;
     public static final int NOT = 3;
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 f76da63..e1d6275 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
@@ -31,14 +31,19 @@
 import java.util.TreeMap;
 import java.util.TreeSet;
 import org.apache.felix.framework.capabilityset.Capability;
+import org.apache.felix.framework.capabilityset.Directive;
 import org.apache.felix.framework.capabilityset.Requirement;
 import org.apache.felix.framework.resolver.Resolver.ResolverState;
+import org.apache.felix.framework.util.Util;
+import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
 
 public class Candidates
 {
     private final Module m_root;
 
+    // Set of all candidate modules.
+    private final Set<Module> m_candidateModules;
     // Maps a capability to requirements that match it.
     private final Map<Capability, Set<Requirement>> m_dependentMap;
     // Maps a requirement to the capability it matches.
@@ -64,12 +69,14 @@
     **/
     private Candidates(
         Module root,
+        Set<Module> candidateModules,
         Map<Capability, Set<Requirement>> dependentMap,
         Map<Requirement, SortedSet<Capability>> candidateMap,
         Map<Capability, Map<String, Map<Version, List<Requirement>>>> hostFragments,
         Map<Module, WrappedModule> wrappedHosts, Map<Module, Object> populateResultCache)
     {
         m_root = root;
+        m_candidateModules = candidateModules;
         m_dependentMap = dependentMap;
         m_candidateMap = candidateMap;
         m_hostFragments = hostFragments;
@@ -85,6 +92,7 @@
     public Candidates(ResolverState state, Module root)
     {
         m_root = root;
+        m_candidateModules = new HashSet<Module>();
         m_dependentMap = new HashMap<Capability, Set<Requirement>>();
         m_candidateMap = new HashMap<Requirement, SortedSet<Capability>>();
         m_hostFragments =
@@ -109,6 +117,7 @@
         Requirement req, SortedSet<Capability> candidates)
     {
         m_root = root;
+        m_candidateModules = new HashSet<Module>();
         m_dependentMap = new HashMap<Capability, Set<Requirement>>();
         m_candidateMap = new HashMap<Requirement, SortedSet<Capability>>();
         m_hostFragments =
@@ -216,7 +225,7 @@
 
             // Get satisfying candidates and populate their candidates if necessary.
             ResolveException rethrow = null;
-            SortedSet<Capability> candidates = state.getCandidates(module, req, true);
+            SortedSet<Capability> candidates = state.getCandidates(req, true);
             for (Iterator<Capability> itCandCap = candidates.iterator(); itCandCap.hasNext(); )
             {
                 Capability candCap = itCandCap.next();
@@ -347,9 +356,17 @@
 
         // Record the candidates.
         m_candidateMap.put(req, candidates);
-        // Add the requirement as a dependent on the candidates.
+
+        // Make a list of all candidate modules for determining singetons.
+        // Add the requirement as a dependent on the candidates. Keep track
+        // of fragments for hosts.
         for (Capability cap : candidates)
         {
+            // Remember the module for all capabilities so we can
+            // determine which ones are singletons.
+            m_candidateModules.add(cap.getModule());
+
+            // Record the requirement as dependent on the capability.
             Set<Requirement> dependents = m_dependentMap.get(cap);
             if (dependents == null)
             {
@@ -357,6 +374,7 @@
                 m_dependentMap.put(cap, dependents);
             }
             dependents.add(req);
+
             // Keep track of hosts and associated fragments.
             if (isFragment)
             {
@@ -435,11 +453,80 @@
      * can attach to two hosts effectively gets multiplied across the two hosts.
      * So, any modules being satisfied by the fragment will end up having the
      * two hosts as potential candidates, rather than the single fragment.
+     * @param existingSingletons existing resolved singletons.
      * @throws ResolveException if the removal of any unselected fragments result
      *         in the root module being unable to resolve.
     **/
-    public void mergeFragments() throws ResolveException
+    public void prepare(List<Module> existingSingletons)
     {
+        final Map<String, Module> singletons = new HashMap<String, Module>();
+
+        for (Iterator<Module> it = m_candidateModules.iterator(); it.hasNext(); )
+        {
+            Module m = it.next();
+            if (isSingleton(m))
+            {
+                // See if there is an existing singleton for the
+                // module's symbolic name.
+                Module singleton = singletons.get(m.getSymbolicName());
+                // If there is no existing singleton or this module is
+                // a resolved singleton or this module has a higher version
+                // and the existing singleton is not resolved, then select
+                // this module as the singleton.
+                if ((singleton == null)
+                    || m.isResolved()
+                    || ((m.getVersion().compareTo(singleton.getVersion()) > 0)
+                        && !singleton.isResolved()))
+                {
+                    singletons.put(m.getSymbolicName(), m);
+                    // Remove the singleton module from the candidates
+                    // if it wasn't selected.
+                    if (singleton != null)
+                    {
+                        removeModule(singleton);
+                    }
+                }
+                else
+                {
+                    removeModule(m);
+                }
+            }
+        }
+
+        // If the root is a singleton, then prefer it over any other singleton.
+        if (isSingleton(m_root))
+        {
+            Module singleton = singletons.get(m_root.getSymbolicName());
+            singletons.put(m_root.getSymbolicName(), m_root);
+            if (singleton != null)
+            {
+                if (singleton.isResolved())
+                {
+                    throw new ResolveException(
+                        "Cannot resolve singleton "
+                        + m_root
+                        + " because "
+                        + singleton
+                        + " singleton is already resolved.",
+                        m_root, null);
+                }
+                removeModule(singleton);
+            }
+        }
+
+        // Make sure selected singletons do not conflict with existing
+        // singletons passed into this method.
+        for (int i = 0; (existingSingletons != null) && (i < existingSingletons.size()); i++)
+        {
+            Module existing = existingSingletons.get(i);
+            Module singleton = singletons.get(existing.getSymbolicName());
+            if ((singleton != null) && (singleton != existing))
+            {
+                singletons.remove(singleton.getSymbolicName());
+                removeModule(singleton);
+            }
+        }
+
         // This method performs the following steps:
         // 1. Select the fragments to attach to a given host.
         // 2. Wrap hosts and attach fragments.
@@ -507,7 +594,7 @@
         // Step 3
         for (Module m : unselectedFragments)
         {
-            unselectFragment(m);
+            removeModule(m);
         }
 
         // Step 4
@@ -546,22 +633,30 @@
     }
 
     /**
-     * Removes a fragment from the internal data structures if it wasn't selected.
-     * This process may cause other modules to become unresolved if they depended
-     * on fragment capabilities and there is no other candidate.
-     * @param fragment the fragment to remove.
-     * @throws ResolveException if removing the fragment caused the resolve to fail.
+     * Removes a module from the internal data structures if it wasn't selected
+     * as a fragment or a singleton. This process may cause other modules to
+     * become unresolved if they depended on the module's capabilities and there
+     * is no other candidate.
+     * @param module the module to remove.
+     * @throws ResolveException if removing the module caused the resolve to fail.
     **/
-    private void unselectFragment(Module fragment) throws ResolveException
+    private void removeModule(Module module) throws ResolveException
     {
+        if (m_root.equals(module))
+        {
+// TODO: SINGLETON RESOLVER - Improve this message.
+            String msg = "Unable to resolve " + m_root;
+            ResolveException ex = new ResolveException(msg, m_root, null);
+            throw ex;
+        }
         Set<Module> unresolvedModules = new HashSet<Module>();
-        remove(fragment, unresolvedModules);
+        remove(module, unresolvedModules);
         while (!unresolvedModules.isEmpty())
         {
             Iterator<Module> it = unresolvedModules.iterator();
-            fragment = it.next();
+            module = it.next();
             it.remove();
-            remove(fragment, unresolvedModules);
+            remove(module, unresolvedModules);
         }
     }
 
@@ -686,6 +781,8 @@
     **/
     public Candidates copy()
     {
+        Set<Module> candidateModules = new HashSet<Module>(m_candidateModules);
+
         Map<Capability, Set<Requirement>> dependentMap =
             new HashMap<Capability, Set<Requirement>>();
         for (Entry<Capability, Set<Requirement>> entry : m_dependentMap.entrySet())
@@ -703,7 +800,7 @@
         }
 
         return new Candidates(
-            m_root, dependentMap, candidateMap,
+            m_root, candidateModules, dependentMap, candidateMap,
             m_hostFragments, m_allWrappedHosts, m_populateResultCache);
     }
 
@@ -741,4 +838,32 @@
         }
         System.out.println("=== END CANDIDATE MAP ===");
     }
-}
\ No newline at end of file
+
+    /**
+     * Returns true if the specified module is a singleton
+     * (i.e., directive singleton:=true).
+     *
+     * @param module the module to check for singleton status.
+     * @return true if the module is a singleton, false otherwise.
+    **/
+    private static boolean isSingleton(Module module)
+    {
+        final List<Capability> modCaps =
+            Util.getCapabilityByNamespace(
+                module, Capability.MODULE_NAMESPACE);
+        if (modCaps == null || modCaps.isEmpty())
+        {
+            return false;
+        }
+        final List<Directive> dirs = modCaps.get(0).getDirectives();
+        for (int dirIdx = 0; (dirs != null) && (dirIdx < dirs.size()); dirIdx++)
+        {
+            if (dirs.get(dirIdx).getName().equalsIgnoreCase(Constants.SINGLETON_DIRECTIVE)
+                && Boolean.valueOf((String) dirs.get(dirIdx).getValue()))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/framework/src/main/java/org/apache/felix/framework/resolver/Resolver.java b/framework/src/main/java/org/apache/felix/framework/resolver/Resolver.java
index 2d8f9e0..9d65b4f 100644
--- a/framework/src/main/java/org/apache/felix/framework/resolver/Resolver.java
+++ b/framework/src/main/java/org/apache/felix/framework/resolver/Resolver.java
@@ -33,8 +33,7 @@
 
     public static interface ResolverState
     {
-        SortedSet<Capability> getCandidates(
-            Module module, Requirement req, boolean obeyMandatory);
+        SortedSet<Capability> getCandidates(Requirement req, boolean obeyMandatory);
         void checkExecutionEnvironment(Module module) throws ResolveException;
         void checkNativeLibraries(Module module) throws ResolveException;
     }
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 a7228e7..aeb8449 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
@@ -73,6 +73,8 @@
                     Candidates allCandidates = new Candidates(state, module);
 
                     // Try to populate optional fragments.
+// TODO: SINGLETON RESOLVER - These optional modules will not be considered
+//       for singleton calculation -- fix this.
                     for (Module fragment : fragments)
                     {
                         try
@@ -86,7 +88,7 @@
                     }
 
                     // Merge any fragments into hosts.
-                    allCandidates.mergeFragments();
+                    allCandidates.prepare(getResolvedSingletons(state));
 
                     // Record the initial candidate permutation.
                      m_usesPermutations.add(allCandidates);
@@ -211,6 +213,8 @@
                 try
                 {
                     // Try to populate optional fragments.
+// TODO: SINGLETON RESOLVER - These optional modules will not be considered
+//       for singleton calculation -- fix this.
                     for (Module fragment : fragments)
                     {
                         try
@@ -224,7 +228,7 @@
                     }
 
                     // Merge any fragments into hosts.
-                    allCandidates.mergeFragments();
+                    allCandidates.prepare(getResolvedSingletons(state));
 
                     // Record the initial candidate permutation.
                      m_usesPermutations.add(allCandidates);
@@ -318,6 +322,25 @@
         return null;
     }
 
+    private static List<Module> getResolvedSingletons(ResolverState state)
+    {
+        Requirement req = new RequirementImpl(
+            null,
+            Capability.SINGLETON_NAMESPACE,
+            Collections.EMPTY_LIST,
+            Collections.EMPTY_LIST);
+        SortedSet<Capability> caps = state.getCandidates(req, true);
+        List<Module> singletons = new ArrayList();
+        for (Capability cap : caps)
+        {
+            if (cap.getModule().isResolved())
+            {
+                singletons.add(cap.getModule());
+            }
+        }
+        return singletons;
+    }
+
     private static Capability getHostCapability(Module m)
     {
         for (Capability c : m.getCapabilities())
@@ -390,7 +413,7 @@
         attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
         Requirement req = new RequirementImpl(
             module, Capability.PACKAGE_NAMESPACE, dirs, attrs);
-        SortedSet<Capability> candidates = state.getCandidates(module, req, false);
+        SortedSet<Capability> candidates = state.getCandidates(req, false);
 
         // First find a dynamic requirement that matches the capabilities.
         Requirement dynReq = null;
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 f718c0e..3ca16aa 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
@@ -112,6 +112,19 @@
                     owner, Capability.HOST_NAMESPACE, new ArrayList<Directive>(0),
                     ((CapabilityImpl) moduleCap).getAttributes()));
             }
+
+            // 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 module or host capabilities
+            // because fragments don't have those capabilities, but fragments
+            // can be singletons too.
+            if (isSingleton(moduleCap))
+            {
+                capList.add(new CapabilityImpl(
+                    owner, Capability.SINGLETON_NAMESPACE, new ArrayList<Directive>(0),
+                    ((CapabilityImpl) moduleCap).getAttributes()));
+            }
         }
 
         // Verify that bundle symbolic name is specified.
@@ -226,6 +239,23 @@
         m_isExtension = checkExtensionBundle(headerMap);
     }
 
+    private static boolean isSingleton(Capability cap)
+    {
+        if (cap.getNamespace().equals(Capability.MODULE_NAMESPACE))
+        {
+            final List<Directive> dirs = cap.getDirectives();
+            for (int dirIdx = 0; (dirs != null) && (dirIdx < dirs.size()); dirIdx++)
+            {
+                if (dirs.get(dirIdx).getName().equalsIgnoreCase(Constants.SINGLETON_DIRECTIVE)
+                    && Boolean.valueOf((String) dirs.get(dirIdx).getValue()))
+                {
+                    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/util/manifestparser/RequirementImpl.java b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/RequirementImpl.java
index aa1adbb..0f039fe 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/RequirementImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/RequirementImpl.java
@@ -189,6 +189,10 @@
         {
             sf = new SimpleFilter(null, filters, SimpleFilter.AND);
         }
+        else if (filters.isEmpty())
+        {
+            sf = new SimpleFilter(null, null, SimpleFilter.MATCH_ALL);
+        }
 
         return sf;
     }