Implements singleton support, but we still need to test it more and perhaps
improve its performance. (FELIX-102)


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@684057 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4SearchPolicyCore.java b/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4SearchPolicyCore.java
index 9471c8b..b4f7892 100755
--- a/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4SearchPolicyCore.java
+++ b/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4SearchPolicyCore.java
@@ -1460,7 +1460,8 @@
         // Test the current potential candidates to determine if they
         // are consistent. Keep looping until we find a consistent
         // set or an exception is thrown.
-        while (!isClassSpaceConsistent(rootModule, moduleMap, cycleMap, candidatesMap))
+        while (!isSingletonConsistent(rootModule, moduleMap, candidatesMap) ||
+            !isClassSpaceConsistent(rootModule, moduleMap, cycleMap, candidatesMap))
         {
             // The incrementCandidateConfiguration() method requires
             // ordered access to the candidates map, so we will create
@@ -1486,6 +1487,146 @@
         }
     }
 
+    /**
+     * This methd checks to see if the target module and any of the candidate
+     * modules to resolve its dependencies violate any singleton constraints.
+     * Actually, it just creates a map of resolved singleton modules and then
+     * delegates all checking to another recursive method.
+     * 
+     * @param targetModule the module that is the root of the tree of modules to check.
+     * @param moduleMap a map to cache the package space of each module.
+     * @param candidatesMap a map containing the all candidates to resolve all
+     *        dependencies for all modules.
+     * @return <tt>true</tt> if all candidates are consistent with respect to singletons,
+     *         <tt>false</tt> otherwise.
+    **/
+    private boolean isSingletonConsistent(IModule targetModule, Map moduleMap, Map candidatesMap)
+    {
+        // Create a map of all resolved singleton modules.
+        Map singletonMap = new HashMap();
+        IModule[] modules = m_factory.getModules();
+        for (int i = 0; (modules != null) && (i < modules.length); i++)
+        {
+            if (isResolved(modules[i]) && isSingleton(modules[i]))
+            {
+                String symName = getBundleSymbolicName(modules[i]);
+                singletonMap.put(symName, symName);
+            }
+        }
+
+        return areCandidatesSingletonConsistent(
+            targetModule, singletonMap, moduleMap, new HashMap(), candidatesMap);
+    }
+
+    /**
+     * This method recursive checks the target module and all of its transitive
+     * dependency modules to verify that they do not violate a singleton constraint.
+     * If the target module is a singleton, then it checks that againts existing
+     * singletons. Then it checks all current unresolved candidates recursively.
+     * 
+     * @param targetModule the module that is the root of the tree of modules to check.
+     * @param singletonMap the current map of singleton symbolic names.
+     * @param moduleMap a map to cache the package space of each module.
+     * @param cycleMap a map to detect cycles.
+     * @param candidatesMap a map containing the all candidates to resolve all
+     *        dependencies for all modules.
+     * @return <tt>true</tt> if all candidates are consistent with respect to singletons,
+     *         <tt>false</tt> otherwise.
+    **/
+    private boolean areCandidatesSingletonConsistent(
+        IModule targetModule, Map singletonMap, Map moduleMap, Map cycleMap, Map candidatesMap)
+    {
+        // If we are in a cycle, then assume true for now.
+        if (cycleMap.get(targetModule) != null)
+        {
+            return true;
+        }
+
+        // Record the target module in the cycle map.
+        cycleMap.put(targetModule, targetModule);
+
+        // Check to see if the targetModule violates a singleton.
+        // If not and it is a singleton, then add it to the singleton
+        // map since it will constrain other singletons.
+        String symName = getBundleSymbolicName(targetModule);
+        boolean isSingleton = isSingleton(targetModule);
+        if (isSingleton && singletonMap.containsKey(symName))
+        {
+            return false;
+        }
+        else if (isSingleton)
+        {
+            singletonMap.put(symName, symName);
+        }
+
+        // Get the package space of the target module.
+        Map pkgMap = null;
+        try
+        {
+            pkgMap = getModulePackages(moduleMap, targetModule, candidatesMap);
+        }
+        catch (ResolveException ex)
+        {
+            m_logger.log(
+                Logger.LOG_DEBUG,
+                "Constraint violation for " + targetModule + " detected.",
+                ex);
+            return false;
+        }
+
+        // Loop through all of the target module's accessible packages and
+        // verify that all package sources are consistent.
+        for (Iterator iter = pkgMap.entrySet().iterator(); iter.hasNext(); )
+        {
+            Map.Entry entry = (Map.Entry) iter.next();
+            // Get the resolved package, which contains the set of all
+            // package sources for the given package.
+            ResolvedPackage rp = (ResolvedPackage) entry.getValue();
+            // Loop through each package source and test if it is consistent.
+            for (int srcIdx = 0; srcIdx < rp.m_sourceList.size(); srcIdx++)
+            {
+                // If the module for this package source is not resolved, then
+                // we have to see if resolving it would violate a singleton
+                // constraint.
+                PackageSource ps = (PackageSource) rp.m_sourceList.get(srcIdx);
+                if (!isResolved(ps.m_module))
+                {
+                    return areCandidatesSingletonConsistent(ps.m_module, singletonMap, moduleMap, cycleMap, candidatesMap);
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if the specified module is a singleton
+     * (i.e., directive singleton:=true in Bundle-SymbolicName).
+     *
+     * @param module the module to check for singleton status.
+     * @return true if the module is a singleton, false otherwise.
+    **/
+    private boolean isSingleton(IModule module)
+    {
+        final ICapability[] modCaps = Util.getCapabilityByNamespace(
+                module, Capability.MODULE_NAMESPACE);
+        if (modCaps == null || modCaps.length == 0)
+        {
+            // this should never happen?
+            return false;
+        }
+        final R4Directive[] dirs = ((Capability) modCaps[0]).getDirectives();
+        for (int dirIdx = 0; (dirs != null) && (dirIdx < dirs.length); dirIdx++)
+        {
+            if (dirs[dirIdx].getName().equalsIgnoreCase(Constants.SINGLETON_DIRECTIVE)
+                && Boolean.valueOf(dirs[dirIdx].getValue()).booleanValue())
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private boolean isClassSpaceConsistent(
         IModule targetModule, Map moduleMap, Map cycleMap, Map candidatesMap)
     {
@@ -3088,16 +3229,11 @@
         public IRequirement m_requirement = null;
         public PackageSource[] m_candidates = null;
         public int m_idx = 0;
-        public boolean m_visited = false;
         public CandidateSet(IModule module, IRequirement requirement, PackageSource[] candidates)
         {
             m_module = module;
             m_requirement = requirement;
             m_candidates = candidates;
-            if (isResolved(m_module))
-            {
-                m_visited = true;
-            }
         }
     }
 
@@ -3545,4 +3681,4 @@
 
         return sb.toString();
     }
-}
+}
\ No newline at end of file
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 35c2fa9..42fa802 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
@@ -20,6 +20,8 @@
 
 import java.io.*;
 
+import java.util.ArrayList;
+import java.util.List;
 import org.apache.felix.framework.util.manifestparser.Capability;
 import org.apache.felix.moduleloader.*;
 import org.osgi.framework.Bundle;
@@ -236,6 +238,27 @@
         return null;
     }
 
+    /**
+     * Returns all the capabilities from a module that has a specified namespace.
+     *
+     * @param module    module providing capabilities
+     * @param namespace capability namespace
+     * @return array of matching capabilities or empty if none found
+     */
+    public static ICapability[] getCapabilityByNamespace(IModule module, String namespace)
+    {
+        final List matching = new ArrayList();
+        final ICapability[] caps = module.getDefinition().getCapabilities();
+        for (int capIdx = 0; (caps != null) && (capIdx < caps.length); capIdx++)
+        {
+            if (caps[capIdx].getNamespace().equals(namespace))
+            {
+                matching.add(caps[capIdx]);
+            }
+        }
+        return (ICapability[]) matching.toArray(new ICapability[matching.size()]);
+    }
+
     public static IWire getWire(IModule m, String name)
     {
         IWire[] wires = m.getWires();
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 814c322..6b3d5a2 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
@@ -797,7 +797,7 @@
                 Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, symName, false);
             attrs[1] = new R4Attribute(
                 Constants.BUNDLE_VERSION_ATTRIBUTE, bundleVersion, false);
-            return new Capability(ICapability.MODULE_NAMESPACE, null, attrs);
+            return new Capability(ICapability.MODULE_NAMESPACE, (R4Directive[]) clauses[0][CLAUSE_DIRECTIVES_INDEX], attrs);
         }
 
         return null;