From f41e8ef9fa5e8079345a9c4544b3d1c071a7e378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Sat, 24 Feb 2024 07:10:43 +0100 Subject: [PATCH] Remove all candidates for a substitution package that are mandatory Currently candidates of a substitution package might be permuted even though they are mandatory for others (e.g. they import a package that can only be fulfilled by this one capability). This now adds a ProblemReduction class that tries to figure out some conflicting options and remove them from the set of items to consider. --- .../org/apache/felix/resolver/Candidates.java | 171 ++++++-- .../felix/resolver/ProblemReduction.java | 387 ++++++++++++++++++ .../apache/felix/resolver/ResolverImpl.java | 46 ++- .../src/org/apache/felix/resolver/Util.java | 55 +++ .../resolver/util/CandidateSelector.java | 10 +- 5 files changed, 624 insertions(+), 45 deletions(-) create mode 100644 bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ProblemReduction.java diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java index bfb1b4681c8..37ab945b396 100644 --- a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java +++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java @@ -18,17 +18,43 @@ */ package org.apache.felix.resolver; -import java.util.*; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; import java.util.concurrent.atomic.AtomicBoolean; - +import java.util.stream.Collectors; import org.apache.felix.resolver.ResolverImpl.PermutationType; import org.apache.felix.resolver.ResolverImpl.ResolveSession; import org.apache.felix.resolver.reason.ReasonException; -import org.apache.felix.resolver.util.*; +import org.apache.felix.resolver.util.CandidateSelector; +import org.apache.felix.resolver.util.CopyOnWriteSet; +import org.apache.felix.resolver.util.OpenHashMap; +import org.apache.felix.resolver.util.OpenHashMapList; +import org.apache.felix.resolver.util.OpenHashMapSet; +import org.apache.felix.resolver.util.ShadowList; +import org.eclipse.osgi.container.ModuleContainer; import org.osgi.framework.Version; -import org.osgi.framework.namespace.*; -import org.osgi.resource.*; +import org.osgi.framework.namespace.HostNamespace; +import org.osgi.framework.namespace.IdentityNamespace; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; +import org.osgi.resource.Wire; +import org.osgi.resource.Wiring; import org.osgi.service.resolver.HostedCapability; import org.osgi.service.resolver.ResolutionException; import org.osgi.service.resolver.ResolveContext; @@ -710,7 +736,7 @@ public Capability getFirstCandidate(Requirement req) return null; } - public void removeFirstCandidate(Requirement req) + public Capability removeFirstCandidate(Requirement req) { CandidateSelector candidates = m_candidateMap.get(req); // Remove the conflicting candidate. @@ -722,6 +748,7 @@ public void removeFirstCandidate(Requirement req) // Update the delta with the removed capability CopyOnWriteSet capPath = m_delta.getOrCompute(req); capPath.add(cap); + return cap; } public CandidateSelector clearMultipleCardinalityCandidates(Requirement req, Collection caps) @@ -1122,9 +1149,24 @@ private void remove(Capability c, Set unresolvedResources) } } - private CandidateSelector removeCandidate(Requirement req, Capability cap) { + CandidateSelector removeCandidate(Requirement req, Capability cap) { CandidateSelector candidates = m_candidateMap.get(req); - candidates.remove(cap); + if (candidates != null) { + if (candidates.isModifiable()) { + candidates.remove(cap); + } else { + List remaining = candidates.getRemainingCandidates().stream().filter(c -> c != cap) + .collect(Collectors.toList()); + if (remaining.isEmpty()) { + m_candidateMap.remove(req); + } else { + candidates = candidates.copy(remaining); + m_candidateMap.put(req, candidates); + } + CopyOnWriteSet capPath = m_delta.getOrCompute(req); + capPath.add(cap); + } + } return candidates; } @@ -1147,7 +1189,15 @@ public Candidates copy() m_delta.deepClone()); } - public void dump(ResolveContext rc) + /** + * Dump the current candidate set to system out + * + * @param rc the resolve context that should be used to look for existing + * wirings + * @param all if true all requirements are printed, if false only those that + * have more than one provider + */ + public void dump(ResolveContext rc, boolean all, PrintStream printStream) { // Create set of all revisions from requirements. Set resources = new CopyOnWriteSet(); @@ -1157,44 +1207,69 @@ public void dump(ResolveContext rc) resources.add(entry.getKey().getResource()); } // Now dump the revisions. - System.out.println("=== BEGIN CANDIDATE MAP ==="); + printStream.println("=== BEGIN CANDIDATE MAP ==="); for (Resource resource : resources) { - Wiring wiring = rc.getWirings().get(resource); - System.out.println(" " + resource - + " (" + ((wiring != null) ? "RESOLVED)" : "UNRESOLVED)")); - List reqs = (wiring != null) - ? wiring.getResourceRequirements(null) - : resource.getRequirements(null); - for (Requirement req : reqs) - { - CandidateSelector candidates = m_candidateMap.get(req); - if ((candidates != null) && (!candidates.isEmpty())) - { - System.out.println(" " + req + ": " + candidates); - } - } - reqs = (wiring != null) - ? Util.getDynamicRequirements(wiring.getResourceRequirements(null)) - : Util.getDynamicRequirements(resource.getRequirements(null)); - for (Requirement req : reqs) - { - CandidateSelector candidates = m_candidateMap.get(req); - if ((candidates != null) && (!candidates.isEmpty())) - { - System.out.println(" " + req + ": " + candidates); - } - } + dumpResource(resource, rc, all, printStream); } - System.out.println("=== END CANDIDATE MAP ==="); + printStream.println("=== END CANDIDATE MAP ==="); } + protected void dumpResource(Resource resource, ResolveContext rc, boolean all, PrintStream printStream) { + Wiring wiring = rc == null ? null : rc.getWirings().get(resource); + List reqs = (wiring != null) ? wiring.getResourceRequirements(null) + : resource.getRequirements(null); + List dreqs = (wiring != null) ? Util.getDynamicRequirements(wiring.getResourceRequirements(null)) + : Util.getDynamicRequirements(resource.getRequirements(null)); + boolean hasMulti = hasMulti(reqs); + printStream.println(" " + (hasMulti ? "[?]" : "[!]") + Util.getResourceName(resource) + " (" + + ((wiring != null) ? "RESOLVED)" : "UNRESOLVED)")); + if (all || hasMulti) { + printRe(reqs, printStream, all); + printRe(dreqs, printStream, all); + } + } + + private boolean hasMulti(List reqs) { + for (Requirement req : reqs) { + CandidateSelector candidates = m_candidateMap.get(req); + if ((candidates != null) && (!candidates.isEmpty())) { + List remaining = candidates.getRemainingCandidates(); + if (remaining.size() > 1) { + return true; + } + } + } + return false; + } + + protected int printRe(List reqs, PrintStream printStream, boolean all) { + int dup = 0; + for (Requirement req : reqs) { + CandidateSelector candidates = m_candidateMap.get(req); + if ((candidates != null) && (!candidates.isEmpty())) { + List remaining = candidates.getRemainingCandidates(); + boolean hasMulti = remaining.size() > 1; + if (all || hasMulti) { + dup++; + printStream.println(" " + (hasMulti ? "[?]" : "[!]") + ModuleContainer.toString(req) + ": "); + for (Capability cap : remaining) { + printStream.println(" " + ModuleContainer.toString(cap)); + } + } + } + } + return dup; + } + public Candidates permutate(Requirement req) { if (!Util.isMultiple(req) && canRemoveCandidate(req)) { Candidates perm = copy(); - perm.removeFirstCandidate(req); + Capability candidate = perm.removeFirstCandidate(req); + ProblemReduction.removeUsesViolations(this, candidate, req); + ProblemReduction.removeUsesViolationsForSingletons(this, req.getResource()); return perm; } return null; @@ -1260,6 +1335,14 @@ public boolean canRemoveCandidate(Requirement req) return false; } + Collection getDependent(Capability capability) { + CopyOnWriteSet set = m_dependentMap.get(capability); + if (set == null) { + return Collections.emptySet(); + } + return set; + } + static class DynamicImportFailed extends ResolutionError { private final Requirement requirement; @@ -1343,4 +1426,18 @@ public ResolutionException toException() { } + public Optional getSubstitutionPackage(Requirement requirement) { + if (PackageNamespace.PACKAGE_NAMESPACE.equals(requirement.getNamespace())) { + List remainingCandidates = getCandidates(requirement); + if (remainingCandidates == null || remainingCandidates.isEmpty()) { + // if no candidate, then there can not be substitution detected... + return Optional.empty(); + } + // now find out if we have a self export here in the list of candidates ... + return remainingCandidates.stream() + .filter(cap -> Objects.equals(cap.getResource(), requirement.getResource())).findFirst(); + } + return Optional.empty(); + } + } diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ProblemReduction.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ProblemReduction.java new file mode 100644 index 00000000000..2fa522180b3 --- /dev/null +++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ProblemReduction.java @@ -0,0 +1,387 @@ +package org.apache.felix.resolver; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; +import org.eclipse.osgi.container.ModuleContainer; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; + +/** + * The idea of the {@link ProblemReduction} class is to strike out + * {@link Capability}s that might satisfy {@link Requirement}s but violates some + * contracts that would lead to a guaranteed unresolvable state, for example + *
    + *
  • When a package is substituted, the export of this bundle is discarded. If + * there is a consumer for this package that has no other alternatives this will + * not resolve and the substitution needs not to be considered.
  • + *
+ */ +class ProblemReduction { + + private static final Capability[] EMPTY_CAPABILITIES = new Capability[0]; + + private static final boolean DEBUG_SUBSTITUTE = false; + + private static final boolean DEBUG_VIOLATES = false; + + private static final boolean DEBUG_SINGLE = false; + + public static void discardSubstitutionPackages(Candidates candidates, List resources) { + for (Resource resource : resources) { + + // Check if the resource has any IMPORT package requirements + List requirements = resource.getRequirements(PackageNamespace.PACKAGE_NAMESPACE); + if (!requirements.isEmpty()) { + for (Requirement requirement : requirements) { + Optional substitutionPackage = candidates.getSubstitutionPackage(requirement); + substitutionPackage.ifPresent(self -> { + Capability[] toRemove = candidates.getCandidates(requirement).stream().filter(c -> c != self) + .toArray(Capability[]::new); + for (Capability remove : toRemove) { + System.out.println("remove " + remove); + candidates.removeCandidate(requirement, remove); + } + }); + } + } + } + } + + public static void removeSubstitutions(Candidates candidates, List resources) { + for (Resource resource : resources) { + + // Check if the resource has any IMPORT package requirements + List requirements = resource.getRequirements(PackageNamespace.PACKAGE_NAMESPACE); + if (!requirements.isEmpty()) { + if (DEBUG_SUBSTITUTE && hasSubstitution(candidates, requirements, resource)) { + System.out.println( + "=== Filter possible substitution candidates for " + Util.getResourceName(resource)); + } + // Now check if this is possibly a substitution package... + Map> usesMap = new HashMap<>(); + for (Requirement requirement : requirements) { + List remainingCandidates = candidates.getCandidates(requirement); + if (remainingCandidates == null || remainingCandidates.isEmpty() + || remainingCandidates.size() == 1) { + // means either only one provider or no providers at all so nothing to check in + // this stage + continue; + } + // now find out if we have a self export here in the list of candidates ... + Optional selfExport = remainingCandidates.stream() + .filter(cap -> Objects.equals(cap.getResource(), resource)).findFirst(); + if (selfExport.isPresent()) { + // if yes we have a substitution package! + Capability exportPackage = selfExport.get(); + Collection dependent = candidates.getDependent(exportPackage); + if (dependent != null && !dependent.isEmpty()) { + remainingCandidates = new ArrayList<>(remainingCandidates); + // there is someone who depends potentially on our package export + remainingCandidates.remove(exportPackage); + for (Requirement depreq : dependent) { + Capability moreMatching = hasMoreMatching(remainingCandidates, depreq); + if (moreMatching == null) { + // this means we are the only provider for the requirement, remove all other + // providers so this package gets NOT substituted anymore + for (Capability capability : remainingCandidates) { + if (DEBUG_SUBSTITUTE) { + System.out.println(Util.getResourceName(capability.getResource()) + + " must be removed as a provider of " + + ModuleContainer.toString(requirement) + " because it would make " + + Util.getResourceName(depreq.getResource()) + + " impossible to resolve as the requirement " + + ModuleContainer.toString(depreq) + " does not match " + + ModuleContainer.toString(capability)); + } + removeSubstitutionCandidate(candidates, requirement, capability, usesMap); + } + } + } + } + } + } + if (!usesMap.isEmpty()) { + // next we need to iteratively remove all conflicting providers a conflict is if + // we have removed a capability that uses as package it possibly can provide as + // a substitution + boolean removed; + do { + removed = false; + for (Entry> entry : usesMap.entrySet()) { + Resource removedResource = entry.getKey(); + Set usedPackages = entry.getValue(); + if (DEBUG_SUBSTITUTE) { + System.out.println(Util.getResourceName(removedResource) + " was removed and uses: "); + for (String usedPackage : usedPackages) { + System.out.println("\t" + usedPackage); + } + } + for (Requirement requirement : requirements) { + List remainingCandidates = candidates.getCandidates(requirement); + if (remainingCandidates == null || remainingCandidates.isEmpty()) { + // this time we also need consider single provider candidates + continue; + } + for (Capability candidate : remainingCandidates.toArray(EMPTY_CAPABILITIES)) { + if (candidate.getResource() == removedResource) { + String pkg = Util.getPackageName(candidate); + if (usedPackages.contains(pkg)) { + if (DEBUG_SUBSTITUTE) { + System.out.println("Resource must be removed as a provider of " + + ModuleContainer.toString(requirement) + + " because is uses the package " + pkg + + " what is not provided anymore: " + + ModuleContainer.toString(candidate)); + } + removed |= removeSubstitutionCandidate(candidates, requirement, candidate, + usesMap); + } + } + } + } + } + System.out.println(); + } while (removed); + } + } + } + + } + + private static boolean hasSubstitution(Candidates candidates, List requirements, Resource resource) { + for (Requirement requirement : requirements) { + List remainingCandidates = candidates.getCandidates(requirement); + if (remainingCandidates == null || remainingCandidates.isEmpty() || remainingCandidates.size() == 1) { + continue; + } + Optional selfExport = remainingCandidates.stream() + .filter(cap -> Objects.equals(cap.getResource(), resource)).findFirst(); + if (selfExport.isPresent()) { + return true; + } + } + return false; + } + + private static boolean removeSubstitutionCandidate(Candidates candidates, Requirement requirement, + Capability capability, Map> usesMap) { + candidates.removeCandidate(requirement, capability); + // everything that this uses must also be removed in a second pass + String uses = capability.getDirectives().get(PackageNamespace.CAPABILITY_USES_DIRECTIVE); + if (uses == null) { + // nothing to do... + return false; + } + boolean added = false; + if (uses != null) { + String[] split = uses.split(","); + for (String usedPackage : split) { + added |= usesMap.computeIfAbsent(capability.getResource(), x -> new HashSet<>()) + .add(usedPackage.trim()); + } + } + return added; + } + + private static Capability hasMoreMatching(List remainingCandidates, Requirement depreq) { + for (Capability candidate : remainingCandidates) { + if (Util.matches(candidate, depreq)) { + return candidate; + } + } + return null; + } + + public static void removeUsesViolations(Candidates candidates, Capability removedCapability, + Requirement requirement) { + if (removedCapability != null) { + Set uses = new TreeSet<>(Util.getUses(removedCapability)); + if (uses.isEmpty()) { + return; + } + Resource requiredResource = requirement.getResource(); + boolean removed = false; + if (DEBUG_VIOLATES) { + System.out.println("=== remove uses violations for " + ModuleContainer.toString(requirement)); + candidates.dumpResource(requiredResource, null, false, System.out); + } + Resource candidateResource = removedCapability.getResource(); + boolean repeat; + int round = 0; + do { + repeat = false; + round++; + if (DEBUG_VIOLATES) { + System.out.println("Round " + round + ":"); + for (String usedPackage : uses) { + System.out.println(" uses: " + usedPackage); + } + } + for (Requirement otherRequirement : requiredResource + .getRequirements(PackageNamespace.PACKAGE_NAMESPACE)) { + if (otherRequirement == requirement) { + continue; + } + List providers = candidates.getCandidates(otherRequirement); + if (candidates != null) { + for (Capability capability : providers.toArray(EMPTY_CAPABILITIES)) { + if (capability.getResource() == candidateResource) { + String packageName = Util.getPackageName(capability); + if (uses.contains(packageName)) { + if (DEBUG_VIOLATES) { + // This means we have a package that is used by the just removed capability, if + // it is provided by the removed resource, we must remove this as well as + // otherwise there is a use constraint violation + System.out.println(" Resource " + Util.getResourceName(candidateResource) + + " must be removed as a provider for " + + ModuleContainer.toString(otherRequirement) + " because the package " + + packageName + " is used by the removed capability " + + ModuleContainer.toString(removedCapability) + ": " + + ModuleContainer.toString(capability)); + removed = true; + candidates.removeCandidate(otherRequirement, capability); + repeat |= uses.addAll(Util.getUses(capability)); + } + } + } + } + } + } + } while (repeat); + if (DEBUG_VIOLATES && removed) { + System.out.println(); + System.out.println("After removal"); + candidates.dumpResource(requiredResource, null, false, System.out); + System.out.println(); + } + } + + } + + public static void removeUsesViolationsForSingletons(Candidates candidates, Resource requiredResource) { + if (DEBUG_SINGLE) { + System.out.println("=== remove singleton uses violations for "); + candidates.dumpResource(requiredResource, null, false, System.out); + } + Map singeltonPackageProvider = new HashMap<>(); + Map multiplePackageProvider = new HashMap<>(); + for (Requirement requirement : requiredResource.getRequirements(PackageNamespace.PACKAGE_NAMESPACE)) { + List list = candidates.getCandidates(requirement); + if (list == null || list.isEmpty()) { + continue; + } + if (list.size() == 1) { + // a single provided capability for a package + Capability candidate = list.get(0); + singeltonPackageProvider.put(Util.getPackageName(candidate), candidate); + } else { + // multiple providers we need to check later + multiplePackageProvider.put(requirement, list.toArray(EMPTY_CAPABILITIES)); + } + } + if (singeltonPackageProvider.isEmpty() || multiplePackageProvider.isEmpty()) { + // nothing to check + return; + } + int round = 0; + do { + round++; + if (DEBUG_SINGLE) { + System.out.println("-- Round " + round + ":"); + } + + } while (removeOneProvider(candidates, singeltonPackageProvider, multiplePackageProvider)); + if (DEBUG_SINGLE) { + System.out.println("Results after reduction "); + candidates.dumpResource(requiredResource, null, false, System.out); + } + } + + protected static boolean removeOneProvider(Candidates candidates, Map singeltonPackageProvider, + Map multiplePackageProvider) { + if (DEBUG_SINGLE) { + System.out.println("Following packages have single providers"); + for (String pkg : singeltonPackageProvider.keySet()) { + System.out.println(" " + pkg); + } + System.out.println("Following requirements have multiple providers"); + for (Entry entry : multiplePackageProvider.entrySet()) { + System.out.println(" " + ModuleContainer.toString(entry.getKey())); + for (Capability capability : entry.getValue()) { + System.out.println(" " + ModuleContainer.toString(capability)); + } + } + } + for (Entry entry : multiplePackageProvider.entrySet()) { + Requirement requirement = entry.getKey(); + Capability[] capabilities = entry.getValue(); + for (Capability capability : capabilities) { + Set uses = Util.getUses(capability); + if (uses.isEmpty()) { + continue; + } + for (String usedPackage : uses) { + Capability singletonCapability = singeltonPackageProvider.get(usedPackage); + if (singletonCapability != null) { + // check if the singleton is provided by one resource that also provides this + // package if yes ... + if (isProvidedBy(singletonCapability, capabilities)) { + // the resources must match or we have a use constraint violation + Resource currentResource = capability.getResource(); + Resource providingResource = singletonCapability.getResource(); + if (providingResource != currentResource) { + if (DEBUG_SINGLE) { + System.out.println("Resource " + Util.getResourceName(currentResource) + + " must be removed as a provider for " + + ModuleContainer.toString(requirement) + " because " + + ModuleContainer.toString(capability) + " uses package " + usedPackage + + " that is already provided by " + + Util.getResourceName(providingResource)); + } + candidates.removeCandidate(requirement, capability); + List remaining = candidates.getCandidates(requirement); + if (remaining == null || remaining.isEmpty()) { + // ??? + multiplePackageProvider.remove(requirement); + } else if (remaining.size() == 1) { + // this now has become a single provider... + Capability newSingelton = remaining.get(0); + singeltonPackageProvider.put(Util.getPackageName(newSingelton), newSingelton); + multiplePackageProvider.remove(requirement); + } else { + // still something more left... + multiplePackageProvider.put(requirement, remaining.toArray(EMPTY_CAPABILITIES)); + } + // restart the round as we now have possible more to use... + return true; + } + } + } + } + } + } + // nothing changes... break out + return false; + } + + private static boolean isProvidedBy(Capability capability, Capability[] capabilities) { + for (Capability other : capabilities) { + if (other.getResource() == capability.getResource()) { + return true; + } + } + return false; + } + +} diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java index 6c9b278db3c..eb7f673de50 100644 --- a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java +++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java @@ -18,19 +18,50 @@ */ package org.apache.felix.resolver; -import java.security.*; -import java.util.*; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; -import java.util.concurrent.*; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicReference; - import org.apache.felix.resolver.reason.ReasonException; import org.apache.felix.resolver.util.ArrayMap; import org.apache.felix.resolver.util.CandidateSelector; import org.apache.felix.resolver.util.OpenHashMap; -import org.osgi.framework.namespace.*; -import org.osgi.resource.*; -import org.osgi.service.resolver.*; +import org.osgi.framework.namespace.BundleNamespace; +import org.osgi.framework.namespace.ExecutionEnvironmentNamespace; +import org.osgi.framework.namespace.HostNamespace; +import org.osgi.framework.namespace.IdentityNamespace; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.resource.Capability; +import org.osgi.resource.Namespace; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; +import org.osgi.resource.Wire; +import org.osgi.resource.Wiring; +import org.osgi.service.resolver.HostedCapability; +import org.osgi.service.resolver.ResolutionException; +import org.osgi.service.resolver.ResolveContext; +import org.osgi.service.resolver.Resolver; public class ResolverImpl implements Resolver { @@ -540,6 +571,7 @@ private void getInitialCandidates(ResolveSession session) { initialCandidates = new Candidates(session); initialCandidates.populate(toPopulate); + ProblemReduction.removeSubstitutions(initialCandidates, toPopulate); } // Merge any fragments into hosts. diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Util.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Util.java index 973cd5b72c6..d1170b71efe 100644 --- a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Util.java +++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Util.java @@ -19,9 +19,17 @@ package org.apache.felix.resolver; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.eclipse.osgi.internal.container.Capabilities; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.Version; import org.osgi.framework.namespace.BundleNamespace; +import org.osgi.framework.namespace.HostNamespace; import org.osgi.framework.namespace.IdentityNamespace; import org.osgi.framework.namespace.PackageNamespace; import org.osgi.resource.Capability; @@ -58,6 +66,14 @@ public static Version getVersion(Resource resource) return null; } + public static String getResourceName(Resource resource) { + String symbolicName = getSymbolicName(resource); + if (symbolicName != null) { + return symbolicName + " " + getVersion(resource); + } + return resource.toString(); + } + public static boolean isFragment(Resource resource) { List caps = resource.getCapabilities(null); @@ -115,4 +131,43 @@ public static List getDynamicRequirements(List reqs) } return result; } + + public static boolean matches(Capability capability, Requirement requirement) { + String namespace = requirement.getNamespace(); + if (!namespace.equals(capability.getNamespace())) { + return false; + } + String filterSpec = requirement.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE); + boolean matchMandatory = PackageNamespace.PACKAGE_NAMESPACE.equals(namespace) + || BundleNamespace.BUNDLE_NAMESPACE.equals(namespace) || HostNamespace.HOST_NAMESPACE.equals(namespace); + try { + return Capabilities.matches(FrameworkUtil.createFilter(filterSpec), capability, matchMandatory); + } catch (InvalidSyntaxException e) { + return false; + } + } + + public static String getPackageName(Capability capability) { + if (capability != null && PackageNamespace.PACKAGE_NAMESPACE.equals(capability.getNamespace())) { + Object object = capability.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE); + if (object instanceof String) { + return (String) object; + } + } + return ""; + } + + public static boolean isExportPackage(Capability capability) { + return capability != null && PackageNamespace.PACKAGE_NAMESPACE.equals(capability.getNamespace()); + } + + public static Set getUses(Capability capability) { + if (capability != null && PackageNamespace.PACKAGE_NAMESPACE.equals(capability.getNamespace())) { + String uses = capability.getDirectives().get(PackageNamespace.CAPABILITY_USES_DIRECTIVE); + if (uses != null && !uses.isEmpty()) { + return Arrays.stream(uses.split(",")).map(String::trim).collect(Collectors.toSet()); + } + } + return Collections.emptySet(); + } } \ No newline at end of file diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/util/CandidateSelector.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/util/CandidateSelector.java index de8a5f502d2..402980f86ce 100644 --- a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/util/CandidateSelector.java +++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/util/CandidateSelector.java @@ -22,7 +22,6 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; - import org.osgi.resource.Capability; public class CandidateSelector { @@ -45,6 +44,10 @@ public CandidateSelector copy() { return new CandidateSelector(this); } + public CandidateSelector copy(List collect) { + return new CandidateSelector(collect, isUnmodifiable); + } + public int getRemainingCandidateCount() { return unmodifiable.size() - currentIndex; } @@ -87,4 +90,9 @@ protected void checkModifiable() { throw new IllegalStateException("Trying to mutate after candidates have been prepared."); } } + + public boolean isModifiable() { + return !isUnmodifiable.get(); + } + }