Applied patch (FELIX-3716) to provide support for resolving multiple cardinality
requirements.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1414522 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java b/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java
index cdcba43..7dc6d4f 100644
--- a/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java
+++ b/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java
@@ -47,13 +47,17 @@
public class ResolverImpl implements Resolver
{
private final Logger m_logger;
-
// Holds candidate permutations based on permutating "uses" chains.
// These permutations are given higher priority.
private final List<Candidates> m_usesPermutations = new ArrayList<Candidates>();
// Holds candidate permutations based on permutating requirement candidates.
// These permutations represent backtracking on previous decisions.
private final List<Candidates> m_importPermutations = new ArrayList<Candidates>();
+ // Holds candidate permutations based on removing candidates that satisfy
+ // multiple cardinality requirements.
+ // This permutation represents a permutation that is consistent because we have
+ // removed the offending capabilities
+ private Candidates m_multipleCardCandidates = null;
public ResolverImpl(Logger logger)
{
@@ -73,7 +77,7 @@
// TODO: RFC-112 - Need impl-specific type.
// Collection<Resource> ondemandFragments = (rc instanceof ResolveContextImpl)
// ? ((ResolveContextImpl) rc).getOndemandResources() : Collections.EMPTY_LIST;
- Collection<Resource> ondemandFragments = Collections.EMPTY_LIST;
+ Collection<Resource> ondemandFragments = Collections.EMPTY_LIST;
boolean retry;
do
@@ -88,7 +92,7 @@
// Populate mandatory resources; since these are mandatory
// resources, failure throws a resolve exception.
for (Iterator<Resource> it = mandatoryResources.iterator();
- it.hasNext(); )
+ it.hasNext();)
{
Resource resource = it.next();
if (Util.isFragment(resource) || (rc.getWirings().get(resource) == null))
@@ -166,6 +170,10 @@
resourcePkgMap.clear();
m_packageSourcesCache.clear();
+ // Null out each time a new permutation is attempted.
+ // We only use this to store a valid permutation which is a
+ // delta of the current permutation.
+ m_multipleCardCandidates = null;
allCandidates = (m_usesPermutations.size() > 0)
? m_usesPermutations.remove(0)
@@ -229,7 +237,7 @@
{
faultyResource =
((WrappedRequirement) faultyReq)
- .getDeclaredRequirement().getResource();
+ .getDeclaredRequirement().getResource();
}
// Try to ignore the faulty resource if it is not mandatory.
if (optionalResources.remove(faultyResource))
@@ -249,6 +257,13 @@
// resolve, so populate the wire map.
else
{
+ if (m_multipleCardCandidates != null)
+ {
+ // Candidates for multiple cardinality requirements were
+ // removed in order to provide a consistent class space.
+ // Use the consistent permutation
+ allCandidates = m_multipleCardCandidates;
+ }
for (Resource resource : allResources)
{
Resource target = resource;
@@ -266,8 +281,8 @@
{
wireMap =
populateWireMap(
- rc, allCandidates.getWrappedHost(target),
- resourcePkgMap, wireMap, allCandidates);
+ rc, allCandidates.getWrappedHost(target),
+ resourcePkgMap, wireMap, allCandidates);
}
}
}
@@ -277,6 +292,7 @@
// Always clear the state.
m_usesPermutations.clear();
m_importPermutations.clear();
+ m_multipleCardCandidates = null;
}
}
while (retry);
@@ -285,25 +301,28 @@
}
/**
- * Resolves a dynamic requirement for the specified host resource using the specified
- * {@link ResolveContext}. The dynamic requirement may contain wild cards in its filter
- * for the package name. The matching candidates are used to resolve the requirement and
- * the resolve context is not asked to find providers for the dynamic requirement.
- * The host resource is expected to not be a fragment, to already be resolved and
- * have an existing wiring provided by the resolve context.
- * <p>
- * This operation may resolve additional resources in order to resolve the dynamic
- * requirement. The returned map will contain entries for each resource that got resolved
- * in addition to the specified host resource. The wire list for the host resource
- * will only contain a single wire which is for the dynamic requirement.
+ * Resolves a dynamic requirement for the specified host resource using the
+ * specified {@link ResolveContext}. The dynamic requirement may contain
+ * wild cards in its filter for the package name. The matching candidates
+ * are used to resolve the requirement and the resolve context is not asked
+ * to find providers for the dynamic requirement. The host resource is
+ * expected to not be a fragment, to already be resolved and have an
+ * existing wiring provided by the resolve context. <p> This operation may
+ * resolve additional resources in order to resolve the dynamic requirement.
+ * The returned map will contain entries for each resource that got resolved
+ * in addition to the specified host resource. The wire list for the host
+ * resource will only contain a single wire which is for the dynamic
+ * requirement.
+ *
* @param rc the resolve context
* @param host the hosting resource
* @param dynamicReq the dynamic requirement
* @param matches a list of matching capabilities
- * @param ondemandFragments collection of on demand fragments that will attach to any host that is a candidate
+ * @param ondemandFragments collection of on demand fragments that will
+ * attach to any host that is a candidate
* @return The new resources and wires required to satisfy the specified
- * dynamic requirement. The returned map is the property of the caller
- * and can be modified by the caller.
+ * dynamic requirement. The returned map is the property of the caller and
+ * can be modified by the caller.
* @throws ResolutionException
*/
public Map<Resource, List<Wire>> resolve(
@@ -422,7 +441,7 @@
{
faultyResource =
((WrappedRequirement) faultyReq)
- .getDeclaredRequirement().getResource();
+ .getDeclaredRequirement().getResource();
}
// Try to ignore the faulty resource if it is not mandatory.
if (ondemandFragments.remove(faultyResource))
@@ -504,10 +523,10 @@
Requirement r = wire.getRequirement();
if (!r.getResource().equals(wire.getRequirer())
|| ((r.getDirectives()
- .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE) != null)
+ .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE) != null)
&& r.getDirectives()
- .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE)
- .equals(PackageNamespace.RESOLUTION_DYNAMIC)))
+ .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE)
+ .equals(PackageNamespace.RESOLUTION_DYNAMIC)))
{
r = new WrappedRequirement(wire.getRequirer(), r);
}
@@ -554,10 +573,7 @@
{
for (Requirement req : resource.getRequirements(null))
{
- String resolution = req.getDirectives()
- .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
- if ((resolution == null)
- || !resolution.equals(PackageNamespace.RESOLUTION_DYNAMIC))
+ if (!Util.isDynamic(req))
{
// Get the candidates for the current requirement.
List<Capability> candCaps = allCandidates.getCandidates(req);
@@ -567,10 +583,24 @@
continue;
}
- // Grab first (i.e., highest priority) candidate.
- Capability cap = candCaps.get(0);
- reqs.add(req);
- caps.add(cap);
+ // For multiple cardinality requirements, we need to grab
+ // all candidates.
+ if (Util.isMultiple(req))
+ {
+ // Use the same requirement, but list each capability separately
+ for (Capability cap : candCaps)
+ {
+ reqs.add(req);
+ caps.add(cap);
+ }
+ }
+ // Grab first (i.e., highest priority) candidate
+ else
+ {
+ Capability cap = candCaps.get(0);
+ reqs.add(req);
+ caps.add(cap);
+ }
}
}
}
@@ -593,8 +623,7 @@
// package, which would be illegal and shouldn't be allowed.
if (isDynamicImporting && ((i + 1) == reqs.size()))
{
- String pkgName = (String)
- cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
+ String pkgName = (String) cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
if (resourcePkgs.m_exportedPkgs.containsKey(pkgName)
|| resourcePkgs.m_importedPkgs.containsKey(pkgName)
|| resourcePkgs.m_requiredPkgs.containsKey(pkgName))
@@ -604,8 +633,7 @@
+ resource
+ " cannot dynamically import package '"
+ pkgName
- + "' since it already has access to it."
- );
+ + "' since it already has access to it.");
}
}
@@ -652,6 +680,7 @@
resourcePkgs,
cap,
blameReqs,
+ cap,
resourcePkgMap,
allCandidates,
usesCycleMap);
@@ -665,7 +694,7 @@
// Ignore resources that import from themselves.
if (!blame.m_cap.getResource().equals(resource))
{
- List<Requirement> blameReqs = new ArrayList();
+ List<Requirement> blameReqs = new ArrayList<Requirement>();
blameReqs.add(blame.m_reqs.get(0));
mergeUses(
@@ -674,6 +703,7 @@
resourcePkgs,
blame.m_cap,
blameReqs,
+ null,
resourcePkgMap,
allCandidates,
usesCycleMap);
@@ -685,7 +715,7 @@
{
for (Blame blame : entry.getValue())
{
- List<Requirement> blameReqs = new ArrayList();
+ List<Requirement> blameReqs = new ArrayList<Requirement>();
blameReqs.add(blame.m_reqs.get(0));
mergeUses(
@@ -694,6 +724,7 @@
resourcePkgs,
blame.m_cap,
blameReqs,
+ null,
resourcePkgMap,
allCandidates,
usesCycleMap);
@@ -758,7 +789,7 @@
{
String value = w.getRequirement()
.getDirectives()
- .get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE);
+ .get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE);
if ((value != null)
&& value.equals(BundleNamespace.VISIBILITY_REEXPORT))
{
@@ -782,7 +813,7 @@
{
String value =
req.getDirectives()
- .get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE);
+ .get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE);
if ((value != null)
&& value.equals(BundleNamespace.VISIBILITY_REEXPORT)
&& (allCandidates.getCandidates(req) != null))
@@ -814,10 +845,9 @@
// Merge the candidate capability into the resource's package space
// for imported or required packages, appropriately.
- String pkgName = (String)
- candCap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
+ String pkgName = (String) candCap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
- List blameReqs = new ArrayList();
+ List<Requirement> blameReqs = new ArrayList<Requirement>();
blameReqs.add(currentReq);
Packages currentPkgs = resourcePkgMap.get(current);
@@ -839,7 +869,7 @@
private void mergeUses(
ResolveContext rc, Resource current, Packages currentPkgs,
- Capability mergeCap, List<Requirement> blameReqs,
+ Capability mergeCap, List<Requirement> blameReqs, Capability matchingCap,
Map<Resource, Packages> resourcePkgMap,
Candidates allCandidates,
Map<Capability, List<Resource>> cycleMap)
@@ -916,26 +946,28 @@
continue;
}
- List<Blame> usedCaps = currentPkgs.m_usedPkgs.get(usedPkgName);
- if (usedCaps == null)
+ List<UsedBlames> usedPkgBlames = currentPkgs.m_usedPkgs.get(usedPkgName);
+ if (usedPkgBlames == null)
{
- usedCaps = new ArrayList<Blame>();
- currentPkgs.m_usedPkgs.put(usedPkgName, usedCaps);
+ usedPkgBlames = new ArrayList<UsedBlames>();
+ currentPkgs.m_usedPkgs.put(usedPkgName, usedPkgBlames);
}
for (Blame blame : candSourceBlames)
{
if (blame.m_reqs != null)
{
- List<Requirement> blameReqs2 = new ArrayList(blameReqs);
+ List<Requirement> blameReqs2 = new ArrayList<Requirement>(blameReqs);
+ // Only add the last requirement in blame chain because
+ // that is the requirement wired to the blamed capability
blameReqs2.add(blame.m_reqs.get(blame.m_reqs.size() - 1));
- usedCaps.add(new Blame(blame.m_cap, blameReqs2));
- mergeUses(rc, current, currentPkgs, blame.m_cap, blameReqs2,
+ addUsedBlame(usedPkgBlames, blame.m_cap, blameReqs2, matchingCap);
+ mergeUses(rc, current, currentPkgs, blame.m_cap, blameReqs2, matchingCap,
resourcePkgMap, allCandidates, cycleMap);
}
else
{
- usedCaps.add(new Blame(blame.m_cap, blameReqs));
- mergeUses(rc, current, currentPkgs, blame.m_cap, blameReqs,
+ addUsedBlame(usedPkgBlames, blame.m_cap, blameReqs, matchingCap);
+ mergeUses(rc, current, currentPkgs, blame.m_cap, blameReqs, matchingCap,
resourcePkgMap, allCandidates, cycleMap);
}
}
@@ -943,6 +975,34 @@
}
}
+ private static void addUsedBlame(
+ List<UsedBlames> usedBlames, Capability usedCap,
+ List<Requirement> blameReqs, Capability matchingCap)
+ {
+ // Create a new Blame based off the used capability and the
+ // blame chain requirements.
+ Blame newBlame = new Blame(usedCap, blameReqs);
+ // Find UsedBlame that uses the same capablity as the new blame.
+ UsedBlames addToBlame = null;
+ for (UsedBlames usedBlame : usedBlames)
+ {
+ if (usedCap.equals(usedBlame.m_cap))
+ {
+ addToBlame = usedBlame;
+ break;
+ }
+ }
+ if (addToBlame == null)
+ {
+ // If none exist create a new UsedBlame for the capability.
+ addToBlame = new UsedBlames(usedCap);
+ usedBlames.add(addToBlame);
+ }
+ // Add the new Blame and record the matching capability cause
+ // in case the root requirement has multiple cardinality.
+ addToBlame.addBlame(newBlame, matchingCap);
+ }
+
private void checkPackageSpaceConsistency(
ResolveContext rc,
Resource resource,
@@ -1036,18 +1096,26 @@
{
continue;
}
- for (Blame usedBlame : pkgs.m_usedPkgs.get(pkgName))
+ for (UsedBlames usedBlames : pkgs.m_usedPkgs.get(pkgName))
{
- if (!isCompatible(rc, exportBlame.m_cap, usedBlame.m_cap, resourcePkgMap))
+ if (!isCompatible(rc, exportBlame.m_cap, usedBlames.m_cap, resourcePkgMap))
{
- // Create a candidate permutation that eliminates all candidates
- // that conflict with existing selected candidates.
- permutation = (permutation != null)
- ? permutation
- : allCandidates.copy();
- rethrow = (rethrow != null)
- ? rethrow
- : new ResolutionException(
+ for (Blame usedBlame : usedBlames.m_blames)
+ {
+ if (checkMultiple(usedBlames, usedBlame, allCandidates))
+ {
+ // Continue to the next usedBlame, if possible we
+ // removed the conflicting candidates.
+ continue;
+ }
+ // Create a candidate permutation that eliminates all candidates
+ // that conflict with existing selected candidates.
+ permutation = (permutation != null)
+ ? permutation
+ : allCandidates.copy();
+ rethrow = (rethrow != null)
+ ? rethrow
+ : new ResolutionException(
"Uses constraint violation. Unable to resolve resource "
+ Util.getSymbolicName(resource)
+ " [" + resource
@@ -1061,33 +1129,38 @@
null,
null);
- mutated = (mutated != null)
- ? mutated
- : new HashSet<Requirement>();
+ mutated = (mutated != null)
+ ? mutated
+ : new HashSet<Requirement>();
- for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--)
- {
- Requirement req = usedBlame.m_reqs.get(reqIdx);
-
- // If we've already permutated this requirement in another
- // uses constraint, don't permutate it again just continue
- // with the next uses constraint.
- if (mutated.contains(req))
+ for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--)
{
- break;
- }
+ Requirement req = usedBlame.m_reqs.get(reqIdx);
+ // Sanity check for multiple.
+ if (Util.isMultiple(req))
+ {
+ continue;
+ }
+ // If we've already permutated this requirement in another
+ // uses constraint, don't permutate it again just continue
+ // with the next uses constraint.
+ if (mutated.contains(req))
+ {
+ break;
+ }
- // See if we can permutate the candidates for blamed
- // requirement; there may be no candidates if the resource
- // associated with the requirement is already resolved.
- List<Capability> candidates = permutation.getCandidates(req);
- if ((candidates != null) && (candidates.size() > 1))
- {
- mutated.add(req);
- // Remove the conflicting candidate.
- candidates.remove(0);
- // Continue with the next uses constraint.
- break;
+ // See if we can permutate the candidates for blamed
+ // requirement; there may be no candidates if the resource
+ // associated with the requirement is already resolved.
+ List<Capability> candidates = permutation.getCandidates(req);
+ if ((candidates != null) && (candidates.size() > 1))
+ {
+ mutated.add(req);
+ // Remove the conflicting candidate.
+ candidates.remove(0);
+ // Continue with the next uses constraint.
+ break;
+ }
}
}
}
@@ -1112,10 +1185,11 @@
// We combine the imported and required packages here into one map.
// Imported packages are added after required packages because they shadow or override
// the packages from required bundles.
- Map<String, List<Blame>> allImportRequirePkgs = new HashMap<String, List<Blame>>(pkgs.m_requiredPkgs);
+ Map<String, List<Blame>> allImportRequirePkgs =
+ new HashMap<String, List<Blame>>(pkgs.m_requiredPkgs);
allImportRequirePkgs.putAll(pkgs.m_importedPkgs);
- for (Entry<String, List<Blame>> pkgEntry: allImportRequirePkgs.entrySet())
+ for (Entry<String, List<Blame>> pkgEntry : allImportRequirePkgs.entrySet())
{
String pkgName = pkgEntry.getKey();
for (Blame requirementBlame : pkgEntry.getValue())
@@ -1124,18 +1198,26 @@
{
continue;
}
- for (Blame usedBlame : pkgs.m_usedPkgs.get(pkgName))
+ for (UsedBlames usedBlames : pkgs.m_usedPkgs.get(pkgName))
{
- if (!isCompatible(rc, requirementBlame.m_cap, usedBlame.m_cap, resourcePkgMap))
+ if (!isCompatible(rc, requirementBlame.m_cap, usedBlames.m_cap, resourcePkgMap))
{
- // Create a candidate permutation that eliminates any candidates
- // that conflict with existing selected candidates.
- permutation = (permutation != null)
- ? permutation
- : allCandidates.copy();
- rethrow = (rethrow != null)
- ? rethrow
- : new ResolutionException(
+ for (Blame usedBlame : usedBlames.m_blames)
+ {
+ if (checkMultiple(usedBlames, usedBlame, allCandidates))
+ {
+ // Continue to the next usedBlame, if possible we
+ // removed the conflicting candidates.
+ continue;
+ }
+ // Create a candidate permutation that eliminates all candidates
+ // that conflict with existing selected candidates.
+ permutation = (permutation != null)
+ ? permutation
+ : allCandidates.copy();
+ rethrow = (rethrow != null)
+ ? rethrow
+ : new ResolutionException(
"Uses constraint violation. Unable to resolve resource "
+ Util.getSymbolicName(resource)
+ " [" + resource
@@ -1154,33 +1236,38 @@
null,
null);
- mutated = (mutated != null)
- ? mutated
- : new HashSet();
+ mutated = (mutated != null)
+ ? mutated
+ : new HashSet<Requirement>();
- for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--)
- {
- Requirement req = usedBlame.m_reqs.get(reqIdx);
-
- // If we've already permutated this requirement in another
- // uses constraint, don't permutate it again just continue
- // with the next uses constraint.
- if (mutated.contains(req))
+ for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--)
{
- break;
- }
+ Requirement req = usedBlame.m_reqs.get(reqIdx);
+ // Sanity check for multiple.
+ if (Util.isMultiple(req))
+ {
+ continue;
+ }
+ // If we've already permutated this requirement in another
+ // uses constraint, don't permutate it again just continue
+ // with the next uses constraint.
+ if (mutated.contains(req))
+ {
+ break;
+ }
- // See if we can permutate the candidates for blamed
- // requirement; there may be no candidates if the resource
- // associated with the requirement is already resolved.
- List<Capability> candidates = permutation.getCandidates(req);
- if ((candidates != null) && (candidates.size() > 1))
- {
- mutated.add(req);
- // Remove the conflicting candidate.
- candidates.remove(0);
- // Continue with the next uses constraint.
- break;
+ // See if we can permutate the candidates for blamed
+ // requirement; there may be no candidates if the resource
+ // associated with the requirement is already resolved.
+ List<Capability> candidates = permutation.getCandidates(req);
+ if ((candidates != null) && (candidates.size() > 1))
+ {
+ mutated.add(req);
+ // Remove the conflicting candidate.
+ candidates.remove(0);
+ // Continue with the next uses constraint.
+ break;
+ }
}
}
}
@@ -1261,16 +1348,46 @@
}
}
+ private boolean checkMultiple(
+ UsedBlames usedBlames,
+ Blame usedBlame,
+ Candidates permutation)
+ {
+ // Check the root requirement to see if it is a multiple cardinality
+ // requirement.
+ List<Capability> candidates = null;
+ Requirement req = usedBlame.m_reqs.get(0);
+ if (Util.isMultiple(req))
+ {
+ // Create a copy of the current permutation so we can remove the
+ // candidates causing the blame.
+ if (m_multipleCardCandidates == null)
+ {
+ m_multipleCardCandidates = permutation.copy();
+ }
+ // Get the current candidate list and remove all the offending root
+ // cause candidates from a copy of the current permutation.
+ candidates = m_multipleCardCandidates.getCandidates(req);
+ candidates.removeAll(usedBlames.getRootCauses(req));
+ }
+ // We only are successful if there is at least one candidate left
+ // for the requirement
+ return (candidates != null) && !candidates.isEmpty();
+ }
+
private static void permutate(
Candidates allCandidates, Requirement req, List<Candidates> permutations)
{
- List<Capability> candidates = allCandidates.getCandidates(req);
- if ((candidates != null) && (candidates.size() > 1))
+ if (!Util.isMultiple(req))
{
- Candidates perm = allCandidates.copy();
- candidates = perm.getCandidates(req);
- candidates.remove(0);
- permutations.add(perm);
+ List<Capability> candidates = allCandidates.getCandidates(req);
+ if ((candidates != null) && (candidates.size() > 1))
+ {
+ Candidates perm = allCandidates.copy();
+ candidates = perm.getCandidates(req);
+ candidates.remove(0);
+ permutations.add(perm);
+ }
}
}
@@ -1384,14 +1501,14 @@
List<Capability> currentSources =
getPackageSources(
- rc,
- currentCap,
- resourcePkgMap);
+ rc,
+ currentCap,
+ resourcePkgMap);
List<Capability> candSources =
getPackageSources(
- rc,
- candCap,
- resourcePkgMap);
+ rc,
+ candCap,
+ resourcePkgMap);
return currentSources.containsAll(candSources)
|| candSources.containsAll(currentSources);
@@ -1530,34 +1647,41 @@
List<Capability> cands = allCandidates.getCandidates(req);
if ((cands != null) && (cands.size() > 0))
{
- Capability cand = cands.get(0);
- // Do not create wires for the osgi.wiring.* namespaces
- // if the provider and requirer are the same resource;
- // allow such wires for non-OSGi wiring namespaces.
- if (!cand.getNamespace().startsWith("osgi.wiring.")
- || !resource.equals(cand.getResource()))
+ for (Capability cand : cands)
{
- if (!rc.getWirings().containsKey(cand.getResource()))
+ // Do not create wires for the osgi.wiring.* namespaces
+ // if the provider and requirer are the same resource;
+ // allow such wires for non-OSGi wiring namespaces.
+ if (!cand.getNamespace().startsWith("osgi.wiring.")
+ || !resource.equals(cand.getResource()))
{
- populateWireMap(rc, cand.getResource(),
- resourcePkgMap, wireMap, allCandidates);
+ if (!rc.getWirings().containsKey(cand.getResource()))
+ {
+ populateWireMap(rc, cand.getResource(),
+ resourcePkgMap, wireMap, allCandidates);
+ }
+ Wire wire = new WireImpl(
+ unwrappedResource,
+ getDeclaredRequirement(req),
+ getDeclaredResource(cand.getResource()),
+ getDeclaredCapability(cand));
+ if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ packageWires.add(wire);
+ }
+ else if (req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE))
+ {
+ bundleWires.add(wire);
+ }
+ else
+ {
+ capabilityWires.add(wire);
+ }
}
- Wire wire = new WireImpl(
- unwrappedResource,
- getDeclaredRequirement(req),
- getDeclaredResource(cand.getResource()),
- getDeclaredCapability(cand));
- if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ if (!Util.isMultiple(req))
{
- packageWires.add(wire);
- }
- else if (req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE))
- {
- bundleWires.add(wire);
- }
- else
- {
- capabilityWires.add(wire);
+ // If not multiple just create a wire for the first candidate.
+ break;
}
}
}
@@ -1598,11 +1722,11 @@
{
fragmentWires.add(
new WireImpl(
- getDeclaredResource(fragment),
- req,
- unwrappedResource,
- unwrappedResource.getCapabilities(
- HostNamespace.HOST_NAMESPACE).get(0)));
+ getDeclaredResource(fragment),
+ req,
+ unwrappedResource,
+ unwrappedResource.getCapabilities(
+ HostNamespace.HOST_NAMESPACE).get(0)));
}
// Otherwise, if the fragment isn't already resolved and
// this is the first time we are seeing it, then create
@@ -1637,10 +1761,10 @@
}
Capability cand = candidates.get(0);
return new WireImpl(
- getDeclaredResource(requirement.getResource()),
- getDeclaredRequirement(requirement),
- getDeclaredResource(cand.getResource()),
- getDeclaredCapability(cand));
+ getDeclaredResource(requirement.getResource()),
+ getDeclaredRequirement(requirement),
+ getDeclaredResource(cand.getResource()),
+ getDeclaredCapability(cand));
}
private static boolean isPayload(Requirement fragmentReq)
@@ -1680,10 +1804,10 @@
packageWires.add(
new WireImpl(
- resource,
- dynReq,
- getDeclaredResource(dynCand.getResource()),
- getDeclaredCapability(dynCand)));
+ resource,
+ dynReq,
+ getDeclaredResource(dynCand.getResource()),
+ getDeclaredCapability(dynCand)));
wireMap.put(resource, packageWires);
@@ -1722,7 +1846,7 @@
System.out.println(" " + entry.getKey() + " - " + entry.getValue());
}
System.out.println(" USED");
- for (Entry<String, List<Blame>> entry : packages.m_usedPkgs.entrySet())
+ for (Entry<String, List<UsedBlames>> entry : packages.m_usedPkgs.entrySet())
{
System.out.println(" " + entry.getKey() + " - " + entry.getValue());
}
@@ -1774,9 +1898,9 @@
.get(PackageNamespace.PACKAGE_NAMESPACE).toString());
Capability usedCap =
getSatisfyingCapability(
- rc,
- allCandidates,
- blame.m_reqs.get(i + 1));
+ rc,
+ allCandidates,
+ blame.m_reqs.get(i + 1));
sb.append("; uses:=");
sb.append(usedCap.getAttributes()
.get(PackageNamespace.PACKAGE_NAMESPACE));
@@ -1798,8 +1922,8 @@
sb.append(export.getAttributes().get(export.getNamespace()).toString());
if (export.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)
&& !export.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE)
- .equals(blame.m_cap.getAttributes().get(
- PackageNamespace.PACKAGE_NAMESPACE)))
+ .equals(blame.m_cap.getAttributes().get(
+ PackageNamespace.PACKAGE_NAMESPACE)))
{
sb.append("; uses:=");
sb.append(blame.m_cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE));
@@ -1867,7 +1991,7 @@
public final Map<String, Blame> m_exportedPkgs = new HashMap();
public final Map<String, List<Blame>> m_importedPkgs = new HashMap();
public final Map<String, List<Blame>> m_requiredPkgs = new HashMap();
- public final Map<String, List<Blame>> m_usedPkgs = new HashMap();
+ public final Map<String, List<UsedBlames>> m_usedPkgs = new HashMap();
public boolean m_isCalculated = false;
public Packages(Resource resource)
@@ -1893,8 +2017,8 @@
return m_cap.getResource()
+ "." + m_cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE)
+ (((m_reqs == null) || m_reqs.isEmpty())
- ? " NO BLAME"
- : " BLAMED ON " + m_reqs);
+ ? " NO BLAME"
+ : " BLAMED ON " + m_reqs);
}
@Override
@@ -1904,4 +2028,80 @@
&& m_cap.equals(((Blame) o).m_cap);
}
}
+
+ /*
+ * UsedBlames hold a list of Blame that have a common used capability.
+ * The UsedBlames stores sets of capabilities (root causes) that match a
+ * root requirement with multiple cardinality. These causes are the
+ * capabilities that pulled in the common used capability.
+ * It is assumed that multiple cardinality requirements can only be
+ * root requirements of a Blame.
+ *
+ * This is only true because capabilities can only use a package
+ * capability. They cannot use any other kind of capability so we
+ * do not have to worry about transitivity of the uses directive
+ * from other capability types.
+ */
+ private static class UsedBlames
+ {
+ public final Capability m_cap;
+ public final List<Blame> m_blames = new ArrayList<ResolverImpl.Blame>();
+ private Map<Requirement, Set<Capability>> m_rootCauses;
+
+ public UsedBlames(Capability cap)
+ {
+ m_cap = cap;
+ }
+
+ public void addBlame(Blame blame, Capability matchingRootCause)
+ {
+ if (!m_cap.equals(blame.m_cap))
+ {
+ throw new IllegalArgumentException(
+ "Attempt to add a blame with a different used capability: "
+ + blame.m_cap);
+ }
+ m_blames.add(blame);
+ if (matchingRootCause != null)
+ {
+ Requirement req = blame.m_reqs.get(0);
+ // Assumption made that the root requirement of the chain is the only
+ // possible multiple cardinality requirement and that the matching root cause
+ // capability is passed down from the beginning of the chain creation.
+ if (Util.isMultiple(req))
+ {
+ // The root requirement is multiple. Need to store the root cause
+ // so that we can find it later in case the used capability which the cause
+ // capability pulled in is a conflict.
+ if (m_rootCauses == null)
+ {
+ m_rootCauses = new HashMap<Requirement, Set<Capability>>();
+ }
+ Set<Capability> rootCauses = m_rootCauses.get(req);
+ if (rootCauses == null)
+ {
+ rootCauses = new HashSet<Capability>();
+ m_rootCauses.put(req, rootCauses);
+ }
+ rootCauses.add(matchingRootCause);
+ }
+ }
+ }
+
+ public Set<Capability> getRootCauses(Requirement req)
+ {
+ if (m_rootCauses == null)
+ {
+ return Collections.EMPTY_SET;
+ }
+ Set<Capability> result = m_rootCauses.get(req);
+ return result == null ? Collections.EMPTY_SET : result;
+ }
+
+ @Override
+ public String toString()
+ {
+ return m_blames.toString();
+ }
+ }
}
\ No newline at end of file
diff --git a/resolver/src/main/java/org/apache/felix/resolver/Util.java b/resolver/src/main/java/org/apache/felix/resolver/Util.java
index dc5a100..84de220 100644
--- a/resolver/src/main/java/org/apache/felix/resolver/Util.java
+++ b/resolver/src/main/java/org/apache/felix/resolver/Util.java
@@ -78,6 +78,18 @@
return Namespace.RESOLUTION_OPTIONAL.equalsIgnoreCase(resolution);
}
+ public static boolean isMultiple(Requirement req)
+ {
+ return Namespace.CARDINALITY_MULTIPLE.equals(req.getDirectives()
+ .get(Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE)) && !isDynamic(req);
+ }
+
+ public static boolean isDynamic(Requirement req)
+ {
+ return PackageNamespace.RESOLUTION_DYNAMIC.equals(req.getDirectives()
+ .get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE));
+ }
+
public static List<Requirement> getDynamicRequirements(List<Requirement> reqs)
{
List<Requirement> result = new ArrayList<Requirement>();
diff --git a/resolver/src/main/java/org/apache/felix/resolver/test/GenericCapability.java b/resolver/src/main/java/org/apache/felix/resolver/test/GenericCapability.java
new file mode 100644
index 0000000..26c6ec1
--- /dev/null
+++ b/resolver/src/main/java/org/apache/felix/resolver/test/GenericCapability.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.resolver.test;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Resource;
+
+class GenericCapability implements Capability
+{
+ private final Resource m_resource;
+ private final String m_namespace;
+ private final Map<String, String> m_dirs;
+ private final Map<String, Object> m_attrs;
+
+ public GenericCapability(Resource resource, String namespace)
+ {
+ m_resource = resource;
+ m_namespace = namespace;
+ m_dirs = new HashMap<String, String>();
+ m_attrs = new HashMap<String, Object>();
+ }
+
+ public String getNamespace()
+ {
+ return m_namespace;
+ }
+
+ public void addDirective(String name, String value)
+ {
+ m_dirs.put(name, value);
+ }
+
+ public Map<String, String> getDirectives()
+ {
+ return m_dirs;
+ }
+
+ public void addAttribute(String name, Object value)
+ {
+ m_attrs.put(name, value);
+ }
+
+ public Map<String, Object> getAttributes()
+ {
+ return m_attrs;
+ }
+
+ public Resource getResource()
+ {
+ return m_resource;
+ }
+
+ @Override
+ public String toString()
+ {
+ return getNamespace() + "; "
+ + getAttributes();
+ }
+}
\ No newline at end of file
diff --git a/resolver/src/main/java/org/apache/felix/resolver/test/GenericRequirement.java b/resolver/src/main/java/org/apache/felix/resolver/test/GenericRequirement.java
new file mode 100644
index 0000000..69737a9
--- /dev/null
+++ b/resolver/src/main/java/org/apache/felix/resolver/test/GenericRequirement.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.resolver.test;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+class GenericRequirement implements Requirement
+{
+ private final Resource m_resource;
+ private final String m_namespace;
+ private final Map<String, String> m_dirs;
+ private final Map<String, Object> m_attrs;
+
+ public GenericRequirement(Resource resource, String namespace)
+ {
+ m_resource = resource;
+ m_namespace = namespace;
+ m_dirs = new HashMap<String, String>();
+ m_attrs = new HashMap<String, Object>();
+ }
+
+ public String getNamespace()
+ {
+ return m_namespace;
+ }
+
+ public void addDirective(String name, String value)
+ {
+ m_dirs.put(name, value);
+ }
+
+ public Map<String, String> getDirectives()
+ {
+ return m_dirs;
+ }
+
+ public void addAttribute(String name, Object value)
+ {
+ m_attrs.put(name, value);
+ }
+
+ public Map<String, Object> getAttributes()
+ {
+ return m_attrs;
+ }
+
+ public Resource getResource()
+ {
+ return m_resource;
+ }
+
+ @Override
+ public String toString()
+ {
+ return getNamespace() + "; "
+ + getDirectives();
+ }
+}
\ No newline at end of file
diff --git a/resolver/src/main/java/org/apache/felix/resolver/test/Main.java b/resolver/src/main/java/org/apache/felix/resolver/test/Main.java
index f273052..a5a975e 100644
--- a/resolver/src/main/java/org/apache/felix/resolver/test/Main.java
+++ b/resolver/src/main/java/org/apache/felix/resolver/test/Main.java
@@ -19,6 +19,7 @@
package org.apache.felix.resolver.test;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -90,6 +91,13 @@
System.out.println("EXPECTED ResolutionException:");
e.printStackTrace(System.out);
}
+
+ System.out.println("\nSCENARIO 6\n");
+ mandatory = populateScenario6(wirings, candMap);
+ rci = new ResolveContextImpl(wirings, candMap, mandatory, Collections.EMPTY_LIST);
+ wireMap = resolver.resolve(rci);
+ System.out.println("RESULT " + wireMap);
+
}
private static List<Resource> populateScenario1(
@@ -224,7 +232,7 @@
ResourceImpl c = new ResourceImpl("C");
c.addRequirement(new BundleRequirement(c, "D"));
c.addCapability(new BundleCapability(c, "C"));
- PackageCapability p2 = new PackageCapability(c, "p1");
+ PackageCapability p2 = new PackageCapability(c, "p2");
p2.addDirective(Namespace.CAPABILITY_USES_DIRECTIVE, "p1");
c.addCapability(p2);
@@ -268,7 +276,7 @@
ResourceImpl c = new ResourceImpl("C");
c.addRequirement(new BundleRequirement(c, "D"));
c.addCapability(new BundleCapability(c, "C"));
- PackageCapability p2 = new PackageCapability(c, "p1");
+ PackageCapability p2 = new PackageCapability(c, "p2");
p2.addDirective(Namespace.CAPABILITY_USES_DIRECTIVE, "p1");
c.addCapability(p2);
@@ -293,4 +301,102 @@
resources.add(x);
return resources;
}
+
+ private static List<Resource> populateScenario6(
+ Map<Resource, Wiring> wirings, Map<Requirement, List<Capability>> candMap)
+ {
+ wirings.clear();
+ candMap.clear();
+
+ ResourceImpl a1 = new ResourceImpl("A");
+ a1.addRequirement(new PackageRequirement(a1, "p1"));
+ a1.addRequirement(new PackageRequirement(a1, "p2"));
+ Requirement a1Req = new GenericRequirement(a1, "generic");
+ a1Req.getDirectives().put(Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE, Namespace.CARDINALITY_MULTIPLE);
+ a1.addRequirement(a1Req);
+
+ ResourceImpl a2 = new ResourceImpl("A");
+ a2.addRequirement(new BundleRequirement(a2, "B"));
+ a2.addRequirement(new BundleRequirement(a2, "C"));
+ Requirement a2Req = new GenericRequirement(a2, "generic");
+ a2Req.getDirectives().put(Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE, Namespace.CARDINALITY_MULTIPLE);
+ a2.addRequirement(a2Req);
+
+ ResourceImpl b1 = new ResourceImpl("B");
+ b1.addCapability(new BundleCapability(b1, "B"));
+ Capability b1_p2 = new PackageCapability(b1, "p2");
+ b1_p2.getDirectives().put(Namespace.CAPABILITY_USES_DIRECTIVE, "p1");
+ b1.addCapability(b1_p2);
+ b1.addRequirement(new PackageRequirement(b1, "p1"));
+
+ ResourceImpl b2 = new ResourceImpl("B");
+ b2.addCapability(new BundleCapability(b2, "B"));
+ Capability b2_p2 = new PackageCapability(b2, "p2");
+ b2_p2.getDirectives().put(Namespace.CAPABILITY_USES_DIRECTIVE, "p1");
+ b2.addCapability(b2_p2);
+ b2.addRequirement(new PackageRequirement(b2, "p1"));
+
+ ResourceImpl c1 = new ResourceImpl("C");
+ c1.addCapability(new BundleCapability(c1, "C"));
+ Capability c1_p1 = new PackageCapability(c1, "p1");
+
+ ResourceImpl c2 = new ResourceImpl("C");
+ c2.addCapability(new BundleCapability(c2, "C"));
+ Capability c2_p1 = new PackageCapability(c2, "p1");
+
+ ResourceImpl d1 = new ResourceImpl("D");
+ GenericCapability d1_generic = new GenericCapability(d1, "generic");
+ d1_generic.addDirective(Namespace.CAPABILITY_USES_DIRECTIVE, "p1,p2");
+ d1.addCapability(d1_generic);
+ d1.addRequirement(new PackageRequirement(d1, "p1"));
+ d1.addRequirement(new PackageRequirement(d1, "p2"));
+
+ ResourceImpl d2 = new ResourceImpl("D");
+ GenericCapability d2_generic = new GenericCapability(d2, "generic");
+ d2_generic.addDirective(Namespace.CAPABILITY_USES_DIRECTIVE, "p1,p2");
+ d2.addCapability(d2_generic);
+ d2.addRequirement(new PackageRequirement(d2, "p1"));
+ d2.addRequirement(new PackageRequirement(d2, "p2"));
+
+ candMap.put(
+ a1.getRequirements(null).get(0),
+ Arrays.asList(c2_p1));
+ candMap.put(
+ a1.getRequirements(null).get(1),
+ Arrays.asList(b2_p2));
+ candMap.put(
+ a1.getRequirements(null).get(2),
+ Arrays.asList((Capability) d1_generic, (Capability) d2_generic));
+ candMap.put(
+ a2.getRequirements(null).get(0),
+ c2.getCapabilities(BundleNamespace.BUNDLE_NAMESPACE));
+ candMap.put(
+ a2.getRequirements(null).get(1),
+ b2.getCapabilities(BundleNamespace.BUNDLE_NAMESPACE));
+ candMap.put(
+ a2.getRequirements(null).get(2),
+ Arrays.asList((Capability) d1_generic, (Capability) d2_generic));
+ candMap.put(
+ b1.getRequirements(null).get(0),
+ Arrays.asList(c1_p1, c2_p1));
+ candMap.put(
+ b2.getRequirements(null).get(0),
+ Arrays.asList(c1_p1, c2_p1));
+ candMap.put(
+ d1.getRequirements(null).get(0),
+ Arrays.asList(c1_p1, c2_p1));
+ candMap.put(
+ d1.getRequirements(null).get(1),
+ Arrays.asList(b1_p2, b2_p2));
+ candMap.put(
+ d2.getRequirements(null).get(0),
+ Arrays.asList(c1_p1, c2_p1));
+ candMap.put(
+ d2.getRequirements(null).get(1),
+ Arrays.asList(b1_p2, b2_p2));
+ List<Resource> resources = new ArrayList<Resource>();
+ resources.add(a1);
+ resources.add(a2);
+ return resources;
+ }
}
\ No newline at end of file