Skip to content

Commit 197ac64

Browse files
committed
Use Capabilities Index
1 parent 4a39b83 commit 197ac64

2 files changed

Lines changed: 288 additions & 30 deletions

File tree

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
package org.eclipse.pde.internal.core;
2+
3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.Collection;
6+
import java.util.Collections;
7+
import java.util.HashMap;
8+
import java.util.HashSet;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.Set;
12+
import java.util.regex.Matcher;
13+
import java.util.regex.Pattern;
14+
15+
import org.eclipse.osgi.internal.framework.FilterImpl;
16+
import org.eclipse.osgi.util.ManifestElement;
17+
import org.osgi.framework.Filter;
18+
import org.osgi.framework.InvalidSyntaxException;
19+
import org.osgi.framework.namespace.AbstractWiringNamespace;
20+
import org.osgi.framework.namespace.BundleNamespace;
21+
import org.osgi.framework.namespace.HostNamespace;
22+
import org.osgi.framework.namespace.PackageNamespace;
23+
import org.osgi.framework.wiring.BundleRevision;
24+
import org.osgi.resource.Capability;
25+
import org.osgi.resource.Namespace;
26+
import org.osgi.resource.Requirement;
27+
28+
// TEMP adjust and reuse from Equinox
29+
public class Capabilities {
30+
static class NamespaceSet {
31+
private final String name;
32+
private final Map<String, Set<Capability>> indexes = new HashMap<>();
33+
private final Set<Capability> all = new HashSet<>();
34+
private final Set<Capability> nonStringIndexes = new HashSet<>(0);
35+
private final boolean matchMandatory;
36+
37+
NamespaceSet(String name) {
38+
this.name = name;
39+
this.matchMandatory = PackageNamespace.PACKAGE_NAMESPACE.equals(name)
40+
|| BundleNamespace.BUNDLE_NAMESPACE.equals(name) || HostNamespace.HOST_NAMESPACE.equals(name);
41+
}
42+
43+
void addCapability(Capability capability) {
44+
if (!name.equals(capability.getNamespace())) {
45+
throw new IllegalArgumentException(
46+
"Invalid namespace: " + capability.getNamespace() + ": expecting: " + name); //$NON-NLS-1$ //$NON-NLS-2$
47+
}
48+
all.add(capability);
49+
// by convention we index by the namespace attribute
50+
Object index = capability.getAttributes().get(name);
51+
if (index == null) {
52+
return;
53+
}
54+
Collection<?> indexCollection = null;
55+
if (index instanceof Collection) {
56+
indexCollection = (Collection<?>) index;
57+
} else if (index.getClass().isArray()) {
58+
indexCollection = Arrays.asList((Object[]) index);
59+
}
60+
if (indexCollection == null) {
61+
addIndex(index, capability);
62+
} else {
63+
for (Object indexKey : indexCollection) {
64+
addIndex(indexKey, capability);
65+
}
66+
}
67+
}
68+
69+
private void addIndex(Object indexKey, Capability capability) {
70+
if (!(indexKey instanceof String)) {
71+
nonStringIndexes.add(capability);
72+
} else {
73+
Set<Capability> capabilities = indexes.get(indexKey);
74+
if (capabilities == null) {
75+
capabilities = new HashSet<>(1);
76+
indexes.put((String) indexKey, capabilities);
77+
}
78+
capabilities.add(capability);
79+
}
80+
}
81+
82+
void removeCapability(Capability capability) {
83+
if (!name.equals(capability.getNamespace())) {
84+
throw new IllegalArgumentException(
85+
"Invalid namespace: " + capability.getNamespace() + ": expecting: " + name); //$NON-NLS-1$//$NON-NLS-2$
86+
}
87+
all.remove(capability);
88+
// by convention we index by the namespace attribute
89+
Object index = capability.getAttributes().get(name);
90+
if (index == null) {
91+
return;
92+
}
93+
Collection<?> indexCollection = null;
94+
if (index instanceof Collection) {
95+
indexCollection = (Collection<?>) index;
96+
} else if (index.getClass().isArray()) {
97+
indexCollection = Arrays.asList((Object[]) index);
98+
}
99+
if (indexCollection == null) {
100+
removeIndex(index, capability);
101+
} else {
102+
for (Object indexKey : indexCollection) {
103+
removeIndex(indexKey, capability);
104+
}
105+
}
106+
}
107+
108+
private void removeIndex(Object indexKey, Capability capability) {
109+
if (!(indexKey instanceof String)) {
110+
nonStringIndexes.remove(capability);
111+
} else {
112+
Set<Capability> capabilities = indexes.get(indexKey);
113+
if (capabilities != null) {
114+
capabilities.remove(capability);
115+
}
116+
}
117+
}
118+
119+
List<Capability> findCapabilities(Requirement requirement) {
120+
if (!name.equals(requirement.getNamespace())) {
121+
throw new IllegalArgumentException(
122+
"Invalid namespace: " + requirement.getNamespace() + ": expecting: " + name); //$NON-NLS-1$//$NON-NLS-2$
123+
}
124+
FilterImpl f = null;
125+
String filterSpec = requirement.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
126+
if (filterSpec != null) {
127+
try {
128+
f = FilterImpl.newInstance(filterSpec);
129+
} catch (InvalidSyntaxException e) {
130+
return Collections.emptyList();
131+
}
132+
}
133+
Object syntheticAttr = requirement.getAttributes().get(SYNTHETIC_REQUIREMENT);
134+
boolean synthetic = syntheticAttr instanceof Boolean ? ((Boolean) syntheticAttr).booleanValue() : false;
135+
136+
List<Capability> result;
137+
if (filterSpec == null) {
138+
result = match(null, all, synthetic);
139+
} else {
140+
String indexKey = f.getPrimaryKeyValue(name);
141+
if (indexKey == null) {
142+
result = match(f, all, synthetic);
143+
} else {
144+
Set<Capability> indexed = indexes.get(indexKey);
145+
if (indexed == null) {
146+
result = new ArrayList<>(0);
147+
} else {
148+
result = match(f, indexed, synthetic);
149+
}
150+
if (!nonStringIndexes.isEmpty()) {
151+
List<Capability> nonStringResult = match(f, nonStringIndexes, synthetic);
152+
for (Capability capability : nonStringResult) {
153+
if (!result.contains(capability)) {
154+
result.add(capability);
155+
}
156+
}
157+
}
158+
}
159+
}
160+
return result;
161+
}
162+
163+
private List<Capability> match(Filter f, Set<Capability> candidates, boolean synthetic) {
164+
List<Capability> result = new ArrayList<>(1);
165+
for (Capability candidate : candidates) {
166+
if (matches(f, candidate, !synthetic && matchMandatory)) {
167+
result.add(candidate);
168+
}
169+
}
170+
return result;
171+
}
172+
}
173+
174+
public static final Pattern MANDATORY_ATTR = Pattern.compile("\\(([^(=<>]+)\\s*[=<>]\\s*[^)]+\\)"); //$NON-NLS-1$
175+
public static final String SYNTHETIC_REQUIREMENT = "org.eclipse.osgi.container.synthetic"; //$NON-NLS-1$
176+
177+
public static boolean matches(Filter f, Capability candidate, boolean matchMandatory) {
178+
if (f != null && !f.matches(candidate.getAttributes())) {
179+
return false;
180+
}
181+
if (matchMandatory) {
182+
// check for mandatory directive
183+
String mandatory = candidate.getDirectives().get(AbstractWiringNamespace.CAPABILITY_MANDATORY_DIRECTIVE);
184+
if (mandatory == null) {
185+
return true;
186+
}
187+
if (f == null) {
188+
return false;
189+
}
190+
Matcher matcher = MANDATORY_ATTR.matcher(f.toString());
191+
String[] mandatoryAttrs = ManifestElement.getArrayFromList(mandatory, ","); //$NON-NLS-1$
192+
boolean allPresent = true;
193+
for (String mandatoryAttr : mandatoryAttrs) {
194+
matcher.reset();
195+
boolean found = false;
196+
while (matcher.find()) {
197+
int numGroups = matcher.groupCount();
198+
for (int i = 1; i <= numGroups; i++) {
199+
if (mandatoryAttr.equals(matcher.group(i))) {
200+
found = true;
201+
}
202+
}
203+
}
204+
allPresent &= found;
205+
}
206+
return allPresent;
207+
}
208+
return true;
209+
}
210+
211+
Map<String, NamespaceSet> namespaceSets = new HashMap<>();
212+
213+
/**
214+
* Adds the {@link BundleRevision#getModuleCapabilities(String)
215+
* capabilities} provided by the specified revision to this database. These
216+
* capabilities must become available for lookup with the
217+
* {@link #findCapabilities(Requirement)} method.
218+
*
219+
* @param revision
220+
* the revision which has capabilities to add
221+
* @return a collection of package names added for the osgi.wiring.package
222+
* namespace
223+
*/
224+
public Collection<String> addCapabilities(BundleRevision revision) {
225+
Collection<String> packageNames = null;
226+
for (Capability capability : revision.getCapabilities(null)) {
227+
NamespaceSet namespaceSet = namespaceSets.get(capability.getNamespace());
228+
if (namespaceSet == null) {
229+
namespaceSet = new NamespaceSet(capability.getNamespace());
230+
namespaceSets.put(capability.getNamespace(), namespaceSet);
231+
}
232+
namespaceSet.addCapability(capability);
233+
// For the package namespace we return a list of package names.
234+
// This is used to clear the dynamic package miss caches.
235+
if (PackageNamespace.PACKAGE_NAMESPACE.equals(capability.getNamespace())) {
236+
Object packageName = capability.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
237+
if (packageName instanceof String) {
238+
if (packageNames == null) {
239+
packageNames = new ArrayList<>();
240+
}
241+
packageNames.add((String) packageName);
242+
}
243+
}
244+
}
245+
return packageNames == null ? Collections.emptyList() : packageNames;
246+
}
247+
248+
/**
249+
* Removes the {@link BundleRevision#getCapabilities(String) capabilities}
250+
* provided by the specified revision from this database. These capabilities
251+
* must no longer be available for lookup with the
252+
* {@link #findCapabilities(Requirement)} method.
253+
*/
254+
public void removeCapabilities(BundleRevision revision) {
255+
for (Capability capability : revision.getCapabilities(null)) {
256+
NamespaceSet namespaceSet = namespaceSets.get(capability.getNamespace());
257+
if (namespaceSet != null) {
258+
namespaceSet.removeCapability(capability);
259+
}
260+
}
261+
}
262+
263+
/**
264+
* Returns a mutable snapshot of capabilities that are candidates for satisfying
265+
* the specified requirement.
266+
*
267+
* @param requirement the requirement
268+
* @return the candidates for the requirement
269+
*/
270+
public List<Capability> findCapabilities(Requirement requirement) {
271+
NamespaceSet namespaceSet = namespaceSets.get(requirement.getNamespace());
272+
if (namespaceSet == null) {
273+
return Collections.emptyList();
274+
}
275+
return namespaceSet.findCapabilities(requirement);
276+
}
277+
}

ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@
2020
import java.util.Arrays;
2121
import java.util.Collection;
2222
import java.util.Collections;
23-
import java.util.HashMap;
2423
import java.util.HashSet;
2524
import java.util.List;
26-
import java.util.Map;
2725
import java.util.Queue;
2826
import java.util.Set;
2927

@@ -38,12 +36,10 @@
3836
import org.osgi.framework.Constants;
3937
import org.osgi.framework.Version;
4038
import org.osgi.framework.namespace.HostNamespace;
41-
import org.osgi.framework.wiring.BundleCapability;
4239
import org.osgi.framework.wiring.BundleRequirement;
4340
import org.osgi.framework.wiring.BundleRevision;
4441
import org.osgi.framework.wiring.BundleWire;
4542
import org.osgi.framework.wiring.BundleWiring;
46-
import org.osgi.resource.Capability;
4743
import org.osgi.resource.Namespace;
4844
import org.osgi.resource.Resource;
4945

@@ -176,13 +172,15 @@ public static Set<BundleDescription> findRequirementsClosure(Collection<BundleDe
176172
throw new AssertionError("Cannot combine INCLUDE_ALL_FRAGMENTS and INCLUDE_NON_TEST_FRAGMENTS"); //$NON-NLS-1$
177173
}
178174

175+
176+
179177
Set<BundleDescription> closure = new HashSet<>(bundles.size() * 4 / 3 + 1);
180178
Queue<BundleDescription> pending = new ArrayDeque<>(bundles.size());
181-
Map<String, List<BundleCapability>> provided = new HashMap<>();
179+
Capabilities capabilities = new Capabilities();
182180

183181
// initialize with given bundles
184182
for (BundleDescription bundle : bundles) {
185-
addNewRequiredBundle(bundle, closure, pending, provided);
183+
addNewRequiredBundle(bundle, closure, pending, capabilities);
186184
}
187185
// perform exhaustive iterative bfs for required wires
188186
while (!pending.isEmpty()) {
@@ -197,7 +195,7 @@ public static Set<BundleDescription> findRequirementsClosure(Collection<BundleDe
197195
// A fragment's host is already required by a wire
198196
for (BundleDescription fragment : bundle.getFragments()) {
199197
if (includeAllFragments || !isTestWorkspaceProject(fragment)) {
200-
addNewRequiredBundle(fragment, closure, pending, provided);
198+
addNewRequiredBundle(fragment, closure, pending, capabilities);
201199
}
202200
}
203201
}
@@ -218,7 +216,7 @@ public static Set<BundleDescription> findRequirementsClosure(Collection<BundleDe
218216
List<BundleWire> requiredWires = wiring.getRequiredWires(null);
219217
for (BundleWire wire : requiredWires) {
220218
BundleRequirement requirement = wire.getRequirement();
221-
if (isSingle(requirement) && isAlreadyProvided(requirement, provided)) {
219+
if (isSingle(requirement) && capabilities.findCapabilities(requirement).size() > 0) {
222220
continue;
223221
}
224222
BundleRevision declaringBundle = requirement.getRevision();
@@ -231,7 +229,7 @@ public static Set<BundleDescription> findRequirementsClosure(Collection<BundleDe
231229
// Use revision of required capability to support the case if
232230
// fragments contribute new packages to their host's API.
233231
if (provider instanceof BundleDescription requiredBundle && (includeOptional || !isOptional(requirement))) {
234-
addNewRequiredBundle(requiredBundle, closure, pending, provided);
232+
addNewRequiredBundle(requiredBundle, closure, pending, capabilities);
235233
}
236234
}
237235
}
@@ -243,29 +241,12 @@ private static boolean isSingle(BundleRequirement requirement) {
243241
.getOrDefault(Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE, Namespace.CARDINALITY_SINGLE));
244242
}
245243

246-
protected static boolean isAlreadyProvided(BundleRequirement requirement,
247-
Map<String, List<BundleCapability>> provided) {
248-
List<BundleCapability> list = provided.get(requirement.getNamespace());
249-
if (list != null && !list.isEmpty()) {
250-
for (BundleCapability bundleCapability : list) {
251-
if (requirement.matches(bundleCapability)) {
252-
return true;
253-
}
254-
}
255-
}
256-
return false;
257-
}
258-
259244
private static void addNewRequiredBundle(BundleDescription bundle, Set<BundleDescription> requiredBundles,
260-
Queue<BundleDescription> pending, Map<String, List<BundleCapability>> provided) {
261-
if (bundle != null && bundle.isResolved() && !bundle.isRemovalPending() && requiredBundles.add(bundle)) {
245+
Queue<BundleDescription> pending, Capabilities capabilities) {
246+
if (bundle != null && bundle.isResolved() && !bundle.isRemovalPending() && requiredBundles.add(bundle)
247+
&& !"org.eclipse.osgi".equals(bundle.getSymbolicName())) { //$NON-NLS-1$
262248
pending.add(bundle);
263-
List<Capability> capabilities = bundle.getCapabilities(null);
264-
for (Capability capability : capabilities) {
265-
if (capability instanceof BundleCapability bc) {
266-
provided.computeIfAbsent(capability.getNamespace(), nil -> new ArrayList<>()).add(bc);
267-
}
268-
}
249+
capabilities.addCapabilities(bundle);
269250
}
270251
}
271252

0 commit comments

Comments
 (0)