Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
package org.eclipse.pde.internal.core;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.osgi.internal.framework.FilterImpl;
import org.eclipse.osgi.util.ManifestElement;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.namespace.AbstractWiringNamespace;
import org.osgi.framework.namespace.BundleNamespace;
import org.osgi.framework.namespace.HostNamespace;
import org.osgi.framework.namespace.PackageNamespace;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.resource.Capability;
import org.osgi.resource.Namespace;
import org.osgi.resource.Requirement;

// TEMP adjust and reuse from Equinox
public class Capabilities {
static class NamespaceSet {
private final String name;
private final Map<String, Set<Capability>> indexes = new HashMap<>();
private final Set<Capability> all = new HashSet<>();
private final Set<Capability> nonStringIndexes = new HashSet<>(0);
private final boolean matchMandatory;

NamespaceSet(String name) {
this.name = name;
this.matchMandatory = PackageNamespace.PACKAGE_NAMESPACE.equals(name)
|| BundleNamespace.BUNDLE_NAMESPACE.equals(name) || HostNamespace.HOST_NAMESPACE.equals(name);
}

void addCapability(Capability capability) {
if (!name.equals(capability.getNamespace())) {
throw new IllegalArgumentException(
"Invalid namespace: " + capability.getNamespace() + ": expecting: " + name); //$NON-NLS-1$ //$NON-NLS-2$
}
all.add(capability);
// by convention we index by the namespace attribute
Object index = capability.getAttributes().get(name);
if (index == null) {
return;
}
Collection<?> indexCollection = null;
if (index instanceof Collection) {
indexCollection = (Collection<?>) index;
} else if (index.getClass().isArray()) {
indexCollection = Arrays.asList((Object[]) index);
}
if (indexCollection == null) {
addIndex(index, capability);
} else {
for (Object indexKey : indexCollection) {
addIndex(indexKey, capability);
}
}
}

private void addIndex(Object indexKey, Capability capability) {
if (!(indexKey instanceof String)) {
nonStringIndexes.add(capability);
} else {
Set<Capability> capabilities = indexes.get(indexKey);
if (capabilities == null) {
capabilities = new HashSet<>(1);
indexes.put((String) indexKey, capabilities);
}
capabilities.add(capability);
}
}

void removeCapability(Capability capability) {
if (!name.equals(capability.getNamespace())) {
throw new IllegalArgumentException(
"Invalid namespace: " + capability.getNamespace() + ": expecting: " + name); //$NON-NLS-1$//$NON-NLS-2$
}
all.remove(capability);
// by convention we index by the namespace attribute
Object index = capability.getAttributes().get(name);
if (index == null) {
return;
}
Collection<?> indexCollection = null;
if (index instanceof Collection) {
indexCollection = (Collection<?>) index;
} else if (index.getClass().isArray()) {
indexCollection = Arrays.asList((Object[]) index);
}
if (indexCollection == null) {
removeIndex(index, capability);
} else {
for (Object indexKey : indexCollection) {
removeIndex(indexKey, capability);
}
}
}

private void removeIndex(Object indexKey, Capability capability) {
if (!(indexKey instanceof String)) {
nonStringIndexes.remove(capability);
} else {
Set<Capability> capabilities = indexes.get(indexKey);
if (capabilities != null) {
capabilities.remove(capability);
}
}
}

List<Capability> findCapabilities(Requirement requirement) {
if (!name.equals(requirement.getNamespace())) {
throw new IllegalArgumentException(
"Invalid namespace: " + requirement.getNamespace() + ": expecting: " + name); //$NON-NLS-1$//$NON-NLS-2$
}
FilterImpl f = null;

Check warning on line 124 in ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/Capabilities.java

View check run for this annotation

Jenkins - eclipse-pde / Compiler

Restriction

NORMAL: Discouraged access: The type 'FilterImpl' is not API (restriction on classpath entry '/home/jenkins/agent/workspace/eclipse.pde_PR-1618/.m2/repository/p2/osgi/bundle/org.eclipse.osgi/3.23.0.v20250228-0640/org.eclipse.osgi-3.23.0.v20250228-0640.jar')
String filterSpec = requirement.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
if (filterSpec != null) {
try {
f = FilterImpl.newInstance(filterSpec);

Check warning on line 128 in ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/Capabilities.java

View check run for this annotation

Jenkins - eclipse-pde / Compiler

Restriction

NORMAL: Discouraged access: The type 'FilterImpl' is not API (restriction on classpath entry '/home/jenkins/agent/workspace/eclipse.pde_PR-1618/.m2/repository/p2/osgi/bundle/org.eclipse.osgi/3.23.0.v20250228-0640/org.eclipse.osgi-3.23.0.v20250228-0640.jar')

Check warning on line 128 in ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/Capabilities.java

View check run for this annotation

Jenkins - eclipse-pde / Compiler

Restriction

NORMAL: Discouraged access: The method 'FilterImpl.newInstance(String)' is not API (restriction on classpath entry '/home/jenkins/agent/workspace/eclipse.pde_PR-1618/.m2/repository/p2/osgi/bundle/org.eclipse.osgi/3.23.0.v20250228-0640/org.eclipse.osgi-3.23.0.v20250228-0640.jar')
} catch (InvalidSyntaxException e) {
return Collections.emptyList();
}
}
Object syntheticAttr = requirement.getAttributes().get(SYNTHETIC_REQUIREMENT);
boolean synthetic = syntheticAttr instanceof Boolean ? ((Boolean) syntheticAttr).booleanValue() : false;

List<Capability> result;
if (filterSpec == null) {
result = match(null, all, synthetic);
} else {
String indexKey = f.getPrimaryKeyValue(name);

Check warning on line 140 in ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/Capabilities.java

View check run for this annotation

Jenkins - eclipse-pde / Compiler

Potential Programming Problem

NORMAL: Potential null pointer access: The variable f may be null at this location

Check warning on line 140 in ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/Capabilities.java

View check run for this annotation

Jenkins - eclipse-pde / Compiler

Restriction

NORMAL: Discouraged access: The method 'FilterImpl.getPrimaryKeyValue(String)' is not API (restriction on classpath entry '/home/jenkins/agent/workspace/eclipse.pde_PR-1618/.m2/repository/p2/osgi/bundle/org.eclipse.osgi/3.23.0.v20250228-0640/org.eclipse.osgi-3.23.0.v20250228-0640.jar')
if (indexKey == null) {
result = match(f, all, synthetic);
} else {
Set<Capability> indexed = indexes.get(indexKey);
if (indexed == null) {
result = new ArrayList<>(0);
} else {
result = match(f, indexed, synthetic);
}
if (!nonStringIndexes.isEmpty()) {
List<Capability> nonStringResult = match(f, nonStringIndexes, synthetic);
for (Capability capability : nonStringResult) {
if (!result.contains(capability)) {
result.add(capability);
}
}
}
}
}
return result;
}

private List<Capability> match(Filter f, Set<Capability> candidates, boolean synthetic) {
List<Capability> result = new ArrayList<>(1);
for (Capability candidate : candidates) {
if (matches(f, candidate, !synthetic && matchMandatory)) {
result.add(candidate);
}
}
return result;
}
}

public static final Pattern MANDATORY_ATTR = Pattern.compile("\\(([^(=<>]+)\\s*[=<>]\\s*[^)]+\\)"); //$NON-NLS-1$
public static final String SYNTHETIC_REQUIREMENT = "org.eclipse.osgi.container.synthetic"; //$NON-NLS-1$

public static boolean matches(Filter f, Capability candidate, boolean matchMandatory) {
if (f != null && !f.matches(candidate.getAttributes())) {
return false;
}
if (matchMandatory) {
// check for mandatory directive
String mandatory = candidate.getDirectives().get(AbstractWiringNamespace.CAPABILITY_MANDATORY_DIRECTIVE);
if (mandatory == null) {
return true;
}
if (f == null) {
return false;
}
Matcher matcher = MANDATORY_ATTR.matcher(f.toString());
String[] mandatoryAttrs = ManifestElement.getArrayFromList(mandatory, ","); //$NON-NLS-1$
boolean allPresent = true;
for (String mandatoryAttr : mandatoryAttrs) {
matcher.reset();
boolean found = false;
while (matcher.find()) {
int numGroups = matcher.groupCount();
for (int i = 1; i <= numGroups; i++) {
if (mandatoryAttr.equals(matcher.group(i))) {
found = true;
}
}
}
allPresent &= found;
}
return allPresent;
}
return true;
}

Map<String, NamespaceSet> namespaceSets = new HashMap<>();

/**
* Adds the {@link BundleRevision#getModuleCapabilities(String)

Check warning on line 214 in ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/Capabilities.java

View check run for this annotation

Jenkins - eclipse-pde / Compiler

Javadoc

NORMAL: Javadoc: The method getModuleCapabilities(String) is undefined for the type BundleRevision

Check warning on line 214 in ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/Capabilities.java

View check run for this annotation

Jenkins - eclipse-pde / JavaDoc

-

ERROR: reference not found
* capabilities} provided by the specified revision to this database. These
* capabilities must become available for lookup with the
* {@link #findCapabilities(Requirement)} method.
*
* @param revision
* the revision which has capabilities to add
* @return a collection of package names added for the osgi.wiring.package
* namespace
*/
public Collection<String> addCapabilities(BundleRevision revision) {
Collection<String> packageNames = null;
for (Capability capability : revision.getCapabilities(null)) {
NamespaceSet namespaceSet = namespaceSets.get(capability.getNamespace());
if (namespaceSet == null) {
namespaceSet = new NamespaceSet(capability.getNamespace());
namespaceSets.put(capability.getNamespace(), namespaceSet);
}
namespaceSet.addCapability(capability);
// For the package namespace we return a list of package names.
// This is used to clear the dynamic package miss caches.
if (PackageNamespace.PACKAGE_NAMESPACE.equals(capability.getNamespace())) {
Object packageName = capability.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
if (packageName instanceof String) {
if (packageNames == null) {
packageNames = new ArrayList<>();
}
packageNames.add((String) packageName);
}
}
}
return packageNames == null ? Collections.emptyList() : packageNames;
}

/**
* Removes the {@link BundleRevision#getCapabilities(String) capabilities}
* provided by the specified revision from this database. These capabilities
* must no longer be available for lookup with the
* {@link #findCapabilities(Requirement)} method.
*/
public void removeCapabilities(BundleRevision revision) {
for (Capability capability : revision.getCapabilities(null)) {
NamespaceSet namespaceSet = namespaceSets.get(capability.getNamespace());
if (namespaceSet != null) {
namespaceSet.removeCapability(capability);
}
}
}

/**
* Returns a mutable snapshot of capabilities that are candidates for satisfying
* the specified requirement.
*
* @param requirement the requirement
* @return the candidates for the requirement
*/
public List<Capability> findCapabilities(Requirement requirement) {
NamespaceSet namespaceSet = namespaceSets.get(requirement.getNamespace());
if (namespaceSet == null) {
return Collections.emptyList();
}
return namespaceSet.findCapabilities(requirement);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.resource.Namespace;
import org.osgi.resource.Resource;

/**
Expand Down Expand Up @@ -171,14 +172,16 @@ public static Set<BundleDescription> findRequirementsClosure(Collection<BundleDe
throw new AssertionError("Cannot combine INCLUDE_ALL_FRAGMENTS and INCLUDE_NON_TEST_FRAGMENTS"); //$NON-NLS-1$
}



Set<BundleDescription> closure = new HashSet<>(bundles.size() * 4 / 3 + 1);
Queue<BundleDescription> pending = new ArrayDeque<>(bundles.size());
Capabilities capabilities = new Capabilities();

// initialize with given bundles
for (BundleDescription bundle : bundles) {
addNewRequiredBundle(bundle, closure, pending);
addNewRequiredBundle(bundle, closure, pending, capabilities);
}

// perform exhaustive iterative bfs for required wires
while (!pending.isEmpty()) {
BundleDescription bundle = pending.remove();
Expand All @@ -192,7 +195,7 @@ public static Set<BundleDescription> findRequirementsClosure(Collection<BundleDe
// A fragment's host is already required by a wire
for (BundleDescription fragment : bundle.getFragments()) {
if (includeAllFragments || !isTestWorkspaceProject(fragment)) {
addNewRequiredBundle(fragment, closure, pending);
addNewRequiredBundle(fragment, closure, pending, capabilities);
}
}
}
Expand All @@ -212,7 +215,11 @@ public static Set<BundleDescription> findRequirementsClosure(Collection<BundleDe

List<BundleWire> requiredWires = wiring.getRequiredWires(null);
for (BundleWire wire : requiredWires) {
BundleRevision declaringBundle = wire.getRequirement().getRevision();
BundleRequirement requirement = wire.getRequirement();
if (isSingle(requirement) && capabilities.findCapabilities(requirement).size() > 0) {
continue;
}
BundleRevision declaringBundle = requirement.getRevision();
if (declaringBundle != bundle && !closure.contains(declaringBundle)) {
// Requirement is declared by an attached fragment, which is
// not included into the closure.
Expand All @@ -221,18 +228,25 @@ public static Set<BundleDescription> findRequirementsClosure(Collection<BundleDe
BundleRevision provider = wire.getCapability().getRevision();
// Use revision of required capability to support the case if
// fragments contribute new packages to their host's API.
if (provider instanceof BundleDescription requiredBundle && (includeOptional || !isOptional(wire.getRequirement()))) {
addNewRequiredBundle(requiredBundle, closure, pending);
if (provider instanceof BundleDescription requiredBundle && (includeOptional || !isOptional(requirement))) {
addNewRequiredBundle(requiredBundle, closure, pending, capabilities);
}
}
}
return closure;
}

private static boolean isSingle(BundleRequirement requirement) {
return Namespace.CARDINALITY_SINGLE.equals(requirement.getDirectives()
.getOrDefault(Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE, Namespace.CARDINALITY_SINGLE));
}

private static void addNewRequiredBundle(BundleDescription bundle, Set<BundleDescription> requiredBundles,
Queue<BundleDescription> pending) {
if (bundle != null && bundle.isResolved() && !bundle.isRemovalPending() && requiredBundles.add(bundle)) {
Queue<BundleDescription> pending, Capabilities capabilities) {
if (bundle != null && bundle.isResolved() && !bundle.isRemovalPending() && requiredBundles.add(bundle)
&& !"org.eclipse.osgi".equals(bundle.getSymbolicName())) { //$NON-NLS-1$
pending.add(bundle);
capabilities.addCapabilities(bundle);
}
}

Expand Down
Loading