Move candidation population to Candidates object since it is
related to candidate processing. (FELIX-2858)
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1078116 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 bca4cba..f116633 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
@@ -32,6 +32,7 @@
import java.util.TreeSet;
import org.apache.felix.framework.capabilityset.Capability;
import org.apache.felix.framework.capabilityset.Requirement;
+import org.apache.felix.framework.resolver.Resolver.ResolverState;
import org.osgi.framework.Version;
public class Candidates
@@ -50,6 +51,8 @@
// Maps a module to its associated wrapped module; this only happens
// when a module being resolved has fragments to attach to it.
private final Map<Module, WrappedModule> m_allWrappedHosts;
+ // Map used when populating candidates to hold intermediate and final results.
+ Map<Module, Object> m_populateResultCache;
/**
* Private copy constructor used by the copy() method.
@@ -64,21 +67,22 @@
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, WrappedModule> wrappedHosts, Map<Module, Object> populateResultCache)
{
m_root = root;
m_dependentMap = dependentMap;
m_candidateMap = candidateMap;
m_hostFragments = hostFragments;
m_allWrappedHosts = wrappedHosts;
+ m_populateResultCache = populateResultCache;
}
/**
- * Constructs a new object with the specified root module. The root module
- * is used to determine if the resolves fails when manipulating the candidates.
- * For example, it may be necessary to remove an unselected fragment, which
- * can cause a ripple effect all the way to the root module. If that happens
- * then the resolve fails.
+ * Constructs a new Candidates object with the specified root module. The root
+ * module is used to determine if the resolves fails when manipulating the
+ * candidates. For example, it may be necessary to remove an unselected
+ * fragment, which can cause a ripple effect all the way to the root module.
+ * If that happens then the resolve fails.
* @param root the root module for the resolve.
**/
public Candidates(Module root)
@@ -89,6 +93,251 @@
m_hostFragments =
new HashMap<Capability, Map<String, Map<Version, List<Requirement>>>>();
m_allWrappedHosts = new HashMap<Module, WrappedModule>();
+ m_populateResultCache = new HashMap<Module, Object>();
+ }
+
+ /**
+ * Constructs a new Candidates object with the specified root module and
+ * starting requirement and matching candidates. This constructor is used
+ * when the root module is performing a dynamic import for the given
+ * requirement and the given potential candidates.
+ * @param root the module with a dynamic import to resolve.
+ * @param req the requirement being resolved.
+ * @param candidates the potential candidates matching the requirement.
+ **/
+ public Candidates(Module root, Requirement req, SortedSet<Capability> candidates)
+ {
+ m_root = root;
+ m_dependentMap = new HashMap<Capability, Set<Requirement>>();
+ m_candidateMap = new HashMap<Requirement, SortedSet<Capability>>();
+ m_hostFragments =
+ new HashMap<Capability, Map<String, Map<Version, List<Requirement>>>>();
+ m_allWrappedHosts = new HashMap<Module, WrappedModule>();
+ m_populateResultCache = new HashMap<Module, Object>();
+
+ add(req, candidates);
+ }
+
+ public void populate(ResolverState state, Module module)
+ {
+ // If we are resolving a dynamic import, then the candidate
+ // map will be pre-populated with the candidates for the
+ // dynamic requirement we need to resolve, so we need to
+ // handle that case a little differently.
+ if (m_candidateMap.isEmpty())
+ {
+ populateInternal(state, module);
+ }
+ else
+ {
+ populateDynamic(state, module);
+ }
+ }
+
+// TODO: FELIX3 - Modify to not be recursive.
+ private void populateInternal(ResolverState state, Module module)
+ {
+ // Determine if we've already calculated this module's candidates.
+ // The result cache will have one of three values:
+ // 1. A resolve exception if we've already attempted to populate the
+ // module's candidates but were unsuccessful.
+ // 2. Boolean.TRUE indicating we've already attempted to populate the
+ // module's candidates and were successful.
+ // 3. An array containing the cycle count, current map of candidates
+ // for already processed requirements, and a list of remaining
+ // requirements whose candidates still need to be calculated.
+ // For case 1, rethrow the exception. For case 2, simply return immediately.
+ // For case 3, this means we have a cycle so we should continue to populate
+ // the candidates where we left off and not record any results globally
+ // until we've popped completely out of the cycle.
+
+ // Keeps track of the number of times we've reentered this method
+ // for the current module.
+ Integer cycleCount = null;
+
+ // Keeps track of the candidates we've already calculated for the
+ // current module's requirements.
+ Map<Requirement, SortedSet<Capability>> localCandidateMap = null;
+
+ // Keeps track of the current module's requirements for which we
+ // haven't yet found candidates.
+ List<Requirement> remainingReqs = null;
+
+ // Get the cache value for the current module.
+ Object cacheValue = m_populateResultCache.get(module);
+
+ // This is case 1.
+ if (cacheValue instanceof ResolveException)
+ {
+ throw (ResolveException) cacheValue;
+ }
+ // This is case 2.
+ else if (cacheValue instanceof Boolean)
+ {
+ return;
+ }
+ // This is case 3.
+ else if (cacheValue != null)
+ {
+ // Increment and get the cycle count.
+ cycleCount = (Integer)
+ (((Object[]) cacheValue)[0]
+ = new Integer(((Integer) ((Object[]) cacheValue)[0]).intValue() + 1));
+ // Get the already populated candidates.
+ localCandidateMap = (Map) ((Object[]) cacheValue)[1];
+ // Get the remaining requirements.
+ remainingReqs = (List) ((Object[]) cacheValue)[2];
+ }
+
+ // If there is no cache value for the current module, then this is
+ // the first time we are attempting to populate its candidates, so
+ // do some one-time checks and initialization.
+ if ((remainingReqs == null) && (localCandidateMap == null))
+ {
+ // Verify that any required execution environment is satisfied.
+ state.checkExecutionEnvironment(module);
+
+ // Verify that any native libraries match the current platform.
+ state.checkNativeLibraries(module);
+
+ // Record cycle count.
+ cycleCount = new Integer(0);
+
+ // Create a local map for populating candidates first, just in case
+ // the module is not resolvable.
+ localCandidateMap = new HashMap();
+
+ // Create a modifiable list of the module's requirements.
+ remainingReqs = new ArrayList(module.getRequirements());
+
+ // Add these value to the result cache so we know we are
+ // in the middle of populating candidates for the current
+ // module.
+ m_populateResultCache.put(module,
+ cacheValue = new Object[] { cycleCount, localCandidateMap, remainingReqs });
+ }
+
+ // If we have requirements remaining, then find candidates for them.
+ while (remainingReqs.size() > 0)
+ {
+ Requirement req = remainingReqs.remove(0);
+
+ // Get satisfying candidates and populate their candidates if necessary.
+ ResolveException rethrow = null;
+ SortedSet<Capability> candidates = state.getCandidates(module, req, true);
+ for (Iterator<Capability> itCandCap = candidates.iterator(); itCandCap.hasNext(); )
+ {
+ Capability candCap = itCandCap.next();
+
+ // If the candidate module is not resolved and not the current
+ // module we are trying to populate, then try to populate the
+ // candidate module as well.
+ // NOTE: Technically, we don't have to check to see if the
+ // candidate module is equal to the current module, 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 (!candCap.getModule().isResolved()
+ && !candCap.getModule().equals(module))
+ {
+ try
+ {
+ populateInternal(state, candCap.getModule());
+ }
+ catch (ResolveException ex)
+ {
+ if (rethrow == null)
+ {
+ rethrow = ex;
+ }
+ // Remove the candidate since we weren't able to
+ // populate its candidates.
+ itCandCap.remove();
+ }
+ }
+ }
+
+ // If there are no candidates for the current requirement
+ // and it is not optional, then create, cache, and throw
+ // a resolve exception.
+ if (candidates.isEmpty() && !req.isOptional())
+ {
+ String msg = "Unable to resolve " + module
+ + ": missing requirement " + req;
+ if (rethrow != null)
+ {
+ msg = msg + " [caused by: " + rethrow.getMessage() + "]";
+ }
+ rethrow = new ResolveException(msg, module, req);
+ m_populateResultCache.put(module, rethrow);
+ throw rethrow;
+ }
+ // If we actually have candidates for the requirement, then
+ // add them to the local candidate map.
+ else if (candidates.size() > 0)
+ {
+ localCandidateMap.put(req, candidates);
+ }
+ }
+
+ // If we are exiting from a cycle then decrement
+ // cycle counter, otherwise record the result.
+ if (cycleCount.intValue() > 0)
+ {
+ ((Object[]) cacheValue)[0] = new Integer(cycleCount.intValue() - 1);
+ }
+ else if (cycleCount.intValue() == 0)
+ {
+ // Record that the module was successfully populated.
+ m_populateResultCache.put(module, Boolean.TRUE);
+
+ // Merge local candidate map into global candidate map.
+ if (localCandidateMap.size() > 0)
+ {
+ add(localCandidateMap);
+ }
+ }
+ }
+
+ private void populateDynamic(ResolverState state, Module module)
+ {
+ // There should be one entry in the candidate map, which are the
+ // the candidates for the matching dynamic requirement. Get the
+ // matching candidates and populate their candidates if necessary.
+ ResolveException rethrow = null;
+ Entry<Requirement, SortedSet<Capability>> entry =
+ m_candidateMap.entrySet().iterator().next();
+ Requirement dynReq = entry.getKey();
+ SortedSet<Capability> candidates = entry.getValue();
+ for (Iterator<Capability> itCandCap = candidates.iterator(); itCandCap.hasNext(); )
+ {
+ Capability candCap = itCandCap.next();
+ if (!candCap.getModule().isResolved())
+ {
+ try
+ {
+ populateInternal(state, candCap.getModule());
+ }
+ catch (ResolveException ex)
+ {
+ if (rethrow == null)
+ {
+ rethrow = ex;
+ }
+ itCandCap.remove();
+ }
+ }
+ }
+
+ if (candidates.isEmpty())
+ {
+ if (rethrow == null)
+ {
+ rethrow = new ResolveException("Dynamic import failed.", module, dynReq);
+ }
+ throw rethrow;
+ }
}
/**
@@ -100,7 +349,7 @@
* @param req the requirement to add.
* @param candidates the candidates matching the requirement.
**/
- public void add(Requirement req, SortedSet<Capability> candidates)
+ private void add(Requirement req, SortedSet<Capability> candidates)
{
boolean isFragment = req.getNamespace().equals(Capability.HOST_NAMESPACE);
@@ -151,7 +400,7 @@
* be further modified by the caller.
* @param candidates the bulk requirements and candidates to add.
**/
- public void add(Map<Requirement, SortedSet<Capability>> candidates)
+ private void add(Map<Requirement, SortedSet<Capability>> candidates)
{
for (Entry<Requirement, SortedSet<Capability>> entry : candidates.entrySet())
{
@@ -183,18 +432,6 @@
}
/**
- * Gets the complete candidate map. This is only used in the dynamic
- * import case, since the requirement is unknown and it just needs the
- * one and only requirement in the map. (This could probably be handled
- * differently or better, but it is sufficient for now.)
- * @return The entire candidate map.
- **/
- public Map<Requirement, SortedSet<Capability>> getCandidateMap()
- {
- return m_candidateMap;
- }
-
- /**
* Merges fragments into their hosts. It does this by wrapping all host
* modules and attaching their selected fragments, removing all unselected
* fragment modules, and replacing all occurrences of the original fragments
@@ -206,8 +443,10 @@
* 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.
+ * @throws ResolveException if the removal of any unselected fragments result
+ * in the root module being unable to resolve.
**/
- public void mergeFragments()
+ public void mergeFragments() throws ResolveException
{
// This method performs the following steps:
// 1. Select the fragments to attach to a given host.
@@ -215,7 +454,7 @@
// 3. Remove any unselected fragments. This is necessary because
// other modules may depend on the capabilities of unselected
// fragments, so we need to remove the unselected fragments and
- // any module that depends on them, which could ultimately cause
+ // any modules that depends on them, which could ultimately cause
// the entire resolve to fail.
// 4. Replace all fragments with any host it was merged into
// (effectively multiplying it).
@@ -472,7 +711,8 @@
}
return new Candidates(
- m_root, dependentMap, candidateMap, m_hostFragments, m_allWrappedHosts);
+ m_root, dependentMap, candidateMap,
+ m_hostFragments, m_allWrappedHosts, m_populateResultCache);
}
public void dump()
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 92c8c2e..59df759 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
@@ -72,21 +72,19 @@
Candidates allCandidates = new Candidates(module);
// Populate all candidates.
- Map<Module, Object> resultCache = new HashMap<Module, Object>();
- populateCandidates(
- state, module, allCandidates, resultCache);
+ allCandidates.populate(state, module);
// Try to populate optional fragments.
for (Module fragment : fragments)
{
try
{
- populateCandidates(state, fragment, allCandidates, resultCache);
+ allCandidates.populate(state, fragment);
}
catch (ResolveException ex)
{
// Ignore, since fragments are optional.
- }
+ }
}
// Merge any fragments into hosts.
@@ -215,20 +213,19 @@
try
{
// Populate all candidates.
- Map<Module, Object> resultCache = new HashMap<Module, Object>();
- populateDynamicCandidates(state, module, allCandidates, resultCache);
+ allCandidates.populate(state, module);
// Try to populate optional fragments.
for (Module fragment : fragments)
{
try
{
- populateCandidates(state, fragment, allCandidates, resultCache);
+ allCandidates.populate(state, fragment);
}
catch (ResolveException ex)
{
// Ignore, since fragments are optional.
- }
+ }
}
// Merge any fragments into hosts.
@@ -435,227 +432,14 @@
candidates.clear();
}
+ Candidates allCandidates = null;
+
if (candidates.size() > 0)
{
- Candidates allCandidates = new Candidates(module);
- allCandidates.add(dynReq, candidates);
- return allCandidates;
+ allCandidates = new Candidates(module, dynReq, candidates);
}
- return null;
- }
-
-// TODO: FELIX3 - Modify to not be recursive.
-// TODO: FRAGMENT RESOLVER - Does it make sense to move this to Candidates?
- private void populateCandidates(
- ResolverState state, Module module, Candidates allCandidates,
- Map<Module, Object> resultCache)
- {
- // Determine if we've already calculated this module's candidates.
- // The result cache will have one of three values:
- // 1. A resolve exception if we've already attempted to populate the
- // module's candidates but were unsuccessful.
- // 2. Boolean.TRUE indicating we've already attempted to populate the
- // module's candidates and were successful.
- // 3. An array containing the cycle count, current map of candidates
- // for already processed requirements, and a list of remaining
- // requirements whose candidates still need to be calculated.
- // For case 1, rethrow the exception. For case 2, simply return immediately.
- // For case 3, this means we have a cycle so we should continue to populate
- // the candidates where we left off and not record any results globally
- // until we've popped completely out of the cycle.
-
- // Keeps track of the number of times we've reentered this method
- // for the current module.
- Integer cycleCount = null;
-
- // Keeps track of the candidates we've already calculated for the
- // current module's requirements.
- Map<Requirement, SortedSet<Capability>> localCandidateMap = null;
-
- // Keeps track of the current module's requirements for which we
- // haven't yet found candidates.
- List<Requirement> remainingReqs = null;
-
- // Get the cache value for the current module.
- Object cacheValue = resultCache.get(module);
-
- // This is case 1.
- if (cacheValue instanceof ResolveException)
- {
- throw (ResolveException) cacheValue;
- }
- // This is case 2.
- else if (cacheValue instanceof Boolean)
- {
- return;
- }
- // This is case 3.
- else if (cacheValue != null)
- {
- // Increment and get the cycle count.
- cycleCount = (Integer)
- (((Object[]) cacheValue)[0]
- = new Integer(((Integer) ((Object[]) cacheValue)[0]).intValue() + 1));
- // Get the already populated candidates.
- localCandidateMap = (Map) ((Object[]) cacheValue)[1];
- // Get the remaining requirements.
- remainingReqs = (List) ((Object[]) cacheValue)[2];
- }
-
- // If there is no cache value for the current module, then this is
- // the first time we are attempting to populate its candidates, so
- // do some one-time checks and initialization.
- if ((remainingReqs == null) && (localCandidateMap == null))
- {
- // Verify that any required execution environment is satisfied.
- state.checkExecutionEnvironment(module);
-
- // Verify that any native libraries match the current platform.
- state.checkNativeLibraries(module);
-
- // Record cycle count.
- cycleCount = new Integer(0);
-
- // Create a local map for populating candidates first, just in case
- // the module is not resolvable.
- localCandidateMap = new HashMap();
-
- // Create a modifiable list of the module's requirements.
- remainingReqs = new ArrayList(module.getRequirements());
-
- // Add these value to the result cache so we know we are
- // in the middle of populating candidates for the current
- // module.
- resultCache.put(module,
- cacheValue = new Object[] { cycleCount, localCandidateMap, remainingReqs });
- }
-
- // If we have requirements remaining, then find candidates for them.
- while (remainingReqs.size() > 0)
- {
- Requirement req = remainingReqs.remove(0);
-
- // Get satisfying candidates and populate their candidates if necessary.
- ResolveException rethrow = null;
- SortedSet<Capability> candidates = state.getCandidates(module, req, true);
- for (Iterator<Capability> itCandCap = candidates.iterator(); itCandCap.hasNext(); )
- {
- Capability candCap = itCandCap.next();
-
- // If the candidate module is not resolved and not the current
- // module we are trying to populate, then try to populate the
- // candidate module as well.
- // NOTE: Technically, we don't have to check to see if the
- // candidate module is equal to the current module, 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 (!candCap.getModule().isResolved()
- && !candCap.getModule().equals(module))
- {
- try
- {
- populateCandidates(state, candCap.getModule(),
- allCandidates, resultCache);
- }
- catch (ResolveException ex)
- {
- if (rethrow == null)
- {
- rethrow = ex;
- }
- // Remove the candidate since we weren't able to
- // populate its candidates.
- itCandCap.remove();
- }
- }
- }
-
- // If there are no candidates for the current requirement
- // and it is not optional, then create, cache, and throw
- // a resolve exception.
- if (candidates.isEmpty() && !req.isOptional())
- {
- String msg = "Unable to resolve " + module
- + ": missing requirement " + req;
- if (rethrow != null)
- {
- msg = msg + " [caused by: " + rethrow.getMessage() + "]";
- }
- rethrow = new ResolveException(msg, module, req);
- resultCache.put(module, rethrow);
- throw rethrow;
- }
- // If we actually have candidates for the requirement, then
- // add them to the local candidate map.
- else if (candidates.size() > 0)
- {
- localCandidateMap.put(req, candidates);
- }
- }
-
- // If we are exiting from a cycle then decrement
- // cycle counter, otherwise record the result.
- if (cycleCount.intValue() > 0)
- {
- ((Object[]) cacheValue)[0] = new Integer(cycleCount.intValue() - 1);
- }
- else if (cycleCount.intValue() == 0)
- {
- // Record that the module was successfully populated.
- resultCache.put(module, Boolean.TRUE);
-
- // Merge local candidate map into global candidate map.
- if (localCandidateMap.size() > 0)
- {
- allCandidates.add(localCandidateMap);
- }
- }
- }
-
- private void populateDynamicCandidates(
- ResolverState state, Module module, Candidates allCandidates,
- Map<Module, Object> resultCache)
- {
- // There should be one entry in the candidate map, which are the
- // the candidates for the matching dynamic requirement. Get the
- // matching candidates and populate their candidates if necessary.
- ResolveException rethrow = null;
- Entry<Requirement, SortedSet<Capability>> entry =
- allCandidates.getCandidateMap().entrySet().iterator().next();
- Requirement dynReq = entry.getKey();
- SortedSet<Capability> candidates = entry.getValue();
- for (Iterator<Capability> itCandCap = candidates.iterator(); itCandCap.hasNext(); )
- {
- Capability candCap = itCandCap.next();
- if (!candCap.getModule().isResolved())
- {
- try
- {
- populateCandidates(state, candCap.getModule(),
- allCandidates, resultCache);
- }
- catch (ResolveException ex)
- {
- if (rethrow == null)
- {
- rethrow = ex;
- }
- itCandCap.remove();
- }
- }
- }
-
- if (candidates.isEmpty())
- {
- if (rethrow == null)
- {
- rethrow = new ResolveException("Dynamic import failed.", module, dynReq);
- }
- throw rethrow;
- }
+ return allCandidates;
}
private void calculatePackageSpaces(