diff --git a/bundles/org.eclipse.osgi.tests/META-INF/MANIFEST.MF b/bundles/org.eclipse.osgi.tests/META-INF/MANIFEST.MF index 047afbde428..99bf13295d0 100644 --- a/bundles/org.eclipse.osgi.tests/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.osgi.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Core OSGi Tests Bundle-SymbolicName: org.eclipse.osgi.tests;singleton:=true -Bundle-Version: 3.20.100.qualifier +Bundle-Version: 3.20.200.qualifier Bundle-Vendor: Eclipse.org Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)", diff --git a/bundles/org.eclipse.osgi.tests/pom.xml b/bundles/org.eclipse.osgi.tests/pom.xml index e032ca99ffc..0361e4dd55e 100644 --- a/bundles/org.eclipse.osgi.tests/pom.xml +++ b/bundles/org.eclipse.osgi.tests/pom.xml @@ -19,7 +19,7 @@ org.eclipse.osgi org.eclipse.osgi.tests - 3.20.100-SNAPSHOT + 3.20.200-SNAPSHOT eclipse-test-plugin diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/TestModuleContainer.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/TestModuleContainer.java index 4bd75eee64c..22f0c87774d 100644 --- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/TestModuleContainer.java +++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/TestModuleContainer.java @@ -3959,7 +3959,8 @@ protected void assertNotMoreThanPermutationCreated(ResolutionReport report, fail("Maximum of " + max + " permutations expected but was " + permutations); } else if (permutations < max) { System.out.println( - "## Permutations are below the threshold, consider adjusting the testcase to assert the lower count!"); + "## Permutations (" + permutations + ") are below the threshold (" + max + + "), consider adjusting the testcase to assert the lower count!"); } return; } @@ -4367,8 +4368,8 @@ private static void assertWires(List required, List... p public void testLocalUseConstraintViolations() throws Exception { ResolutionReport result = resolveTestSet("set1"); // TODO get down permutation count! - assertSucessfulWith(result, 52); - assertNotMoreThanPermutationCreated(result, ResolutionReport::getSubstitutionPermutations, 23); + assertSucessfulWith(result, 49); + assertNotMoreThanPermutationCreated(result, ResolutionReport::getSubstitutionPermutations, 20); } private ResolutionReport resolveTestSet(String name) throws Exception { diff --git a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF index 73c86e2b391..9d40447fffe 100644 --- a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Bundle-ManifestVersion: 2 Export-Package: org.eclipse.core.runtime.adaptor;x-friends:="org.eclipse.core.runtime", org.eclipse.core.runtime.internal.adaptor;x-internal:=true, org.eclipse.equinox.log;version="1.1";uses:="org.osgi.framework,org.osgi.service.log", - org.eclipse.osgi.container;version="1.7.0"; + org.eclipse.osgi.container;version="1.8.0"; uses:="org.eclipse.osgi.report.resolution, org.osgi.framework.wiring, org.eclipse.osgi.framework.eventmgr, @@ -107,7 +107,7 @@ Bundle-Activator: org.eclipse.osgi.internal.framework.SystemBundleActivator Bundle-Description: %systemBundle Bundle-Copyright: %copyright Bundle-Vendor: %eclipse.org -Bundle-Version: 3.21.0.qualifier +Bundle-Version: 3.21.100.qualifier Bundle-Localization: systembundle Bundle-DocUrl: http://www.eclipse.org Eclipse-ExtensibleAPI: true diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java index d93eb6d3a06..2386a8b4b13 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java @@ -261,6 +261,34 @@ public static String toString(Capability capability) { return Constants.PROVIDE_CAPABILITY + ": " + capability.toString(); //$NON-NLS-1$ } + /** + * Generates a human readable string representation of the the given + * {@link Resource} using the IDENTITY_NAMESPACE + * + * @param resource the {@link Resource} for which a string representation is + * desired + * + * @since 3.21 + */ + public static String toString(Resource resource) { + String id = null; + Version version = null; + List caps = resource.getCapabilities(null); + for (Capability cap : caps) { + if (cap.getNamespace().equals(IdentityNamespace.IDENTITY_NAMESPACE)) { + id = cap.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE).toString(); + version = (Version) cap.getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE); + } + } + if (id != null) { + if (version != null) { + return String.format("%s %s", id, version); //$NON-NLS-1$ + } + return id; + } + return resource.toString(); + } + private static String createOSGiCapability(Capability cap) { Map attributes = new HashMap<>(cap.getAttributes()); Map directives = cap.getDirectives(); diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java index 6efc6216841..eb1b0dcd4f9 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java @@ -16,6 +16,7 @@ import static org.eclipse.osgi.internal.container.NamespaceList.WIRE; import java.security.Permission; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -37,6 +38,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import org.apache.felix.resolver.Logger; import org.apache.felix.resolver.PermutationType; import org.apache.felix.resolver.ResolutionError; @@ -504,6 +506,74 @@ public void logUsesConstraintViolation(Resource resource, ResolutionError error) } } + public void logRequirement(String message, Requirement requirement) { + debug(String.format(message, ModuleContainer.toString(requirement))); + } + + public void logCapability(String message, Capability requirement) { + debug(String.format(message, ModuleContainer.toString(requirement))); + } + + @Override + public void logCandidates(Resource resource, Function> candidateLookup) { + if (DEBUG_USES) { + Wiring wiring = getWirings().get(resource); + List reqs = (wiring != null) ? wiring.getResourceRequirements(null) + : resource.getRequirements(null); + List dreqs = (wiring != null) + ? getDynamicRequirements(wiring.getResourceRequirements(null)) + : getDynamicRequirements(resource.getRequirements(null)); + boolean hasMulti = hasMulti(reqs, candidateLookup); + Debug.println(String.format(" %s%s (%s)", getMultiMarker(hasMulti), //$NON-NLS-1$ + ModuleContainer.toString(resource), + ((wiring != null) ? "RESOLVED)" : "UNRESOLVED)"))); //$NON-NLS-1$ //$NON-NLS-2$ + printRe(reqs, candidateLookup); + printRe(dreqs, candidateLookup); + } + } + + private String getMultiMarker(boolean hasMulti) { + return hasMulti ? "[?]" : "[!]"; //$NON-NLS-1$ //$NON-NLS-2$ + } + + private boolean hasMulti(List reqs, Function> candidateLookup) { + for (Requirement req : reqs) { + List remaining = candidateLookup.apply(req); + if (remaining.size() > 1) { + return true; + } + } + return false; + } + + private int printRe(List reqs, Function> candidateLookup) { + int dup = 0; + for (Requirement req : reqs) { + List remaining = candidateLookup.apply(req); + boolean hasMulti = remaining.size() > 1; + dup++; + Debug.println(MessageFormat.format(" {0}{1}: ", getMultiMarker(hasMulti), //$NON-NLS-1$ + ModuleContainer.toString(req))); + for (Capability cap : remaining) { + Debug.println(String.format(" %s", ModuleContainer.toString(cap))); //$NON-NLS-1$ + } + } + return dup; + } + + private List getDynamicRequirements(List reqs) { + List result = new ArrayList<>(); + if (reqs != null) { + for (Requirement req : reqs) { + String resolution = req.getDirectives().get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE); + if ((resolution != null) && resolution.equals(PackageNamespace.RESOLUTION_DYNAMIC)) { + result.add(req); + } + } + } + return result; + } + Map getUsesConstraintViolations() { return errors == null ? Collections.emptyMap() : errors; } 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 0b59f6dac0b..25aab4e6072 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,6 +18,7 @@ */ package org.apache.felix.resolver; +import java.io.PrintStream; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicBoolean; @@ -34,6 +35,8 @@ class Candidates { + private static final boolean FILTER_USES = Boolean + .parseBoolean(System.getProperty("felix.resolver.candidates.filteruses", "true")); static class PopulateResult { boolean success; ResolutionError error; @@ -709,7 +712,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. @@ -721,6 +724,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) @@ -1145,54 +1149,15 @@ public Candidates copy() m_delta.deepClone()); } - public void dump(ResolveContext rc) - { - // Create set of all revisions from requirements. - Set resources = new CopyOnWriteSet(); - for (Entry entry - : m_candidateMap.entrySet()) - { - resources.add(entry.getKey().getResource()); - } - // Now dump the revisions. - System.out.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); - } - } - } - System.out.println("=== END CANDIDATE MAP ==="); - } - public Candidates permutate(Requirement req) { if (!Util.isMultiple(req) && canRemoveCandidate(req)) { Candidates perm = copy(); - perm.removeFirstCandidate(req); + Capability removed = perm.removeFirstCandidate(req); + if (FILTER_USES) { + ProblemReduction.removeUsesViolations(perm, req, m_session.getLogger()); + } return perm; } return null; @@ -1341,4 +1306,25 @@ public ResolutionException toException() { } + /** + * Returns the current provided {@link Capability} for the given resource if it + * is a candidate for the {@link Requirement} + * + * @param resource the resource to check + * @param requirement the requirement to check + * @return the {@link Capability} this Resource currently provides for the given + * {@link Requirement} or null if none is provided. + */ + public Capability getCapability(Resource resource, Requirement requirement) { + List providers = getCandidates(requirement); + if (providers != null) { + for (Capability capability : providers) { + if (capability.getResource().equals(resource)) { + return capability; + } + } + } + return null; + } + } diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Logger.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Logger.java index b82c0d9e424..f4a6eebaed1 100644 --- a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Logger.java +++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Logger.java @@ -18,6 +18,10 @@ */ package org.apache.felix.resolver; +import java.util.List; +import java.util.function.Function; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; import org.osgi.resource.Resource; /** @@ -132,6 +136,31 @@ public void logUsesConstraintViolation(Resource resource, ResolutionError error) // do nothing by default } + /** + * Called to debug the current mapping of {@link Requirement}s to + * {@link Capability}s in a resolve operation + * + * @param resource the resource for this log message + * @param candidateLookup a mapping between a requirement and a list of all + * current candidate {@link Capability}s eligible for + * resolving + */ + public void logCandidates(Resource resource, + Function> candidateLookup) + { + // do nothing by default + } + + public void logRequirement(String message, Requirement requirement) + { + debug(String.format(message, requirement)); + } + + public void logCapability(String message, Capability requirement) + { + debug(String.format(message, requirement)); + } + /** * Called whenever a new permutation is added by the resolver. * 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..fea2cab9f0d --- /dev/null +++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ProblemReduction.java @@ -0,0 +1,126 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +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. + */ +class ProblemReduction { + + private static final Capability[] EMPTY_CAPABILITIES = new Capability[0]; + + /** + * Removes all violating providers for a given {@link Requirement} and + * {@link Candidates} in a local search, that is if the requirement has any uses + * it checks if there are other packages used by this one and removes any + * offending providers from the top of the list. + * + * @param candidates candidates to filter + * @param requirement the requirement where the search should start + * @return a list of Candidates that where dropped as part of the filtering + */ + static List removeUsesViolations(Candidates candidates, Requirement requirement, Logger logger) { + Resource targetResource = requirement.getResource(); + // fetch the current candidate for this requirement + Capability currentCandidate = candidates.getFirstCandidate(requirement); + Resource candidateResource = currentCandidate.getResource(); + // now check if it has any uses constraints + Set uses = new TreeSet<>(Util.getUses(currentCandidate)); + if (uses.isEmpty()) { + // there is nothing this one can conflict in this current set of candidates + return Collections.emptyList(); + } + + + if (logger.isDebugEnabled()) { + logger.logRequirement("=== remove uses violations for %s", requirement); + logger.logCapability("== current candidate is %s", currentCandidate); + logger.logCandidates(targetResource, req -> getCapabilityList(candidates, req)); + } + boolean repeat; + int round = 0; + List dropped = new ArrayList<>(); + do { + repeat = false; + round++; + if (logger.isDebugEnabled()) { + logger.debug("Round " + round + ":"); + for (String usedPackage : uses) { + logger.debug(" uses: " + usedPackage); + } + } + // now look at all other imports of the target resource if it is a package that + // is part of a used package + for (Requirement packageRequirement : targetResource.getRequirements(PackageNamespace.PACKAGE_NAMESPACE)) { + if (packageRequirement == requirement) { + continue; + } + Capability providedPackage = candidates.getCapability(candidateResource, packageRequirement); + if (providedPackage == null) { + // we do not provide anything for this package + continue; + } + if (uses.contains(Util.getPackageName(providedPackage))) { + // this is a package where we are a candidate and that has a uses constraint, so + // this package must be provided by us as well or we run into a uses-violation + // later on! + Capability capability = removeViolators(candidates, candidateResource, packageRequirement, dropped); + // if we have added any additional uses we need to reiterate... + repeat |= uses.addAll(Util.getUses(capability)); + } + } + } while (repeat); + if (logger.isDebugEnabled() && !dropped.isEmpty()) { + logger.debug("After removal (" + dropped.size() + " dropped)"); + logger.logCandidates(targetResource, req -> getCapabilityList(candidates, req)); + } + return dropped; + } + + private static Capability removeViolators(Candidates candidates, Resource candidateResource, + Requirement packageRequirement, List dropped) { + Capability capability; + while ((capability = candidates.getFirstCandidate(packageRequirement)).getResource() != candidateResource) { + dropped.add(candidates.copy()); + candidates.removeFirstCandidate(packageRequirement); + } + return capability; + } + + private static List getCapabilityList(Candidates candidates, Requirement requirement) { + List list = candidates.getCandidates(requirement); + if (list == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(list); + } + +} 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 77963fbe4d8..0a5d0a4347f 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 java.util.function.ToIntFunction; 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 { @@ -138,6 +169,10 @@ ConcurrentMap> getUsesCache() { return m_usesCache; } + public Logger getLogger() { + return logger; + } + void permutateIfNeeded(PermutationType type, Requirement req, Candidates permutation) { List candidates = permutation.getCandidates(req); if ((candidates != null) && (candidates.size() > 1)) 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..37b6775ce77 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 @@ -18,8 +18,11 @@ */ 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.osgi.framework.Version; import org.osgi.framework.namespace.BundleNamespace; import org.osgi.framework.namespace.IdentityNamespace; @@ -81,13 +84,13 @@ public static boolean isOptional(Requirement req) public static boolean isMultiple(Requirement req) { - return Namespace.CARDINALITY_MULTIPLE.equals(req.getDirectives() + 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() + return PackageNamespace.RESOLUTION_DYNAMIC.equals(req.getDirectives() .get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE)); } @@ -97,22 +100,23 @@ public static boolean isReexport(Requirement req) .get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE)); } - public static List getDynamicRequirements(List reqs) - { - List result = new ArrayList(); - if (reqs != null) - { - for (Requirement req : reqs) - { - String resolution = req.getDirectives() - .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE); - if ((resolution != null) - && resolution.equals(PackageNamespace.RESOLUTION_DYNAMIC)) - { - result.add(req); - } + 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 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 result; + return Collections.emptySet(); } } \ No newline at end of file diff --git a/bundles/org.eclipse.osgi/pom.xml b/bundles/org.eclipse.osgi/pom.xml index 1d0c3458435..c8d0db88c02 100644 --- a/bundles/org.eclipse.osgi/pom.xml +++ b/bundles/org.eclipse.osgi/pom.xml @@ -19,7 +19,7 @@ org.eclipse.osgi org.eclipse.osgi - 3.21.0-SNAPSHOT + 3.21.100-SNAPSHOT eclipse-plugin diff --git a/features/org.eclipse.equinox.core.feature/feature.xml b/features/org.eclipse.equinox.core.feature/feature.xml index 9b30abc82ff..c79a9fa006b 100644 --- a/features/org.eclipse.equinox.core.feature/feature.xml +++ b/features/org.eclipse.equinox.core.feature/feature.xml @@ -2,7 +2,7 @@ diff --git a/features/org.eclipse.equinox.core.sdk/feature.xml b/features/org.eclipse.equinox.core.sdk/feature.xml index 8c08f312424..ea7111dc955 100644 --- a/features/org.eclipse.equinox.core.sdk/feature.xml +++ b/features/org.eclipse.equinox.core.sdk/feature.xml @@ -2,7 +2,7 @@ diff --git a/features/org.eclipse.equinox.server.core/feature.xml b/features/org.eclipse.equinox.server.core/feature.xml index 0d4b17fe006..9ee3851b51d 100644 --- a/features/org.eclipse.equinox.server.core/feature.xml +++ b/features/org.eclipse.equinox.server.core/feature.xml @@ -2,7 +2,7 @@