diff --git a/bundles/org.eclipse.osgi.tests/META-INF/MANIFEST.MF b/bundles/org.eclipse.osgi.tests/META-INF/MANIFEST.MF
index 48e1b899f23..14ce523fdbf 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.19.100.qualifier
+Bundle-Version: 3.19.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 d3e9d4bab10..beabd708d5f 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.19.100-SNAPSHOT
+ 3.19.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 5c4ecd9f507..d5482329f91 100644
--- a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF
@@ -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.20.0.qualifier
+Bundle-Version: 3.20.100.qualifier
Bundle-Localization: systembundle
Bundle-DocUrl: http://www.eclipse.org
Eclipse-ExtensibleAPI: true
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..f4dc399f28f 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,7 +1149,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();
@@ -1155,36 +1167,59 @@ 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)
+ dumpResource(resource, rc, all, printStream);
+ }
+ 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);
- for (Requirement req : reqs)
- {
- CandidateSelector candidates = m_candidateMap.get(req);
- if ((candidates != null) && (!candidates.isEmpty()))
- {
- System.out.println(" " + req + ": " + candidates);
+ 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;
}
}
- 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);
+ }
+ 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 ? "[?]" : "[!]") + Util.toString(req) + ": ");
+ for (Capability cap : remaining) {
+ printStream.println(" " + Util.toString(cap));
+ }
}
}
}
- System.out.println("=== END CANDIDATE MAP ===");
+ return dup;
}
public Candidates permutate(Requirement req)
@@ -1192,7 +1227,10 @@ 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);
+ }
return perm;
}
return null;
@@ -1341,4 +1379,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/ProblemReduction.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ProblemReduction.java
new file mode 100644
index 00000000000..d1c954126ff
--- /dev/null
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ProblemReduction.java
@@ -0,0 +1,121 @@
+/*
+ * 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.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.
+ */
+class ProblemReduction {
+
+ private static final Capability[] EMPTY_CAPABILITIES = new Capability[0];
+
+ private static final boolean DEBUG_VIOLATES = false;
+
+ /**
+ * 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) {
+ 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 (DEBUG_VIOLATES) {
+ System.out.println("=== remove uses violations for " + ModuleContainer.toString(requirement));
+ System.out.println("== current candidate is " + ModuleContainer.toString(currentCandidate));
+ candidates.dumpResource(targetResource, null, true, System.out);
+ }
+ boolean repeat;
+ int round = 0;
+ List dropped = new ArrayList<>();
+ do {
+ repeat = false;
+ round++;
+ if (DEBUG_VIOLATES) {
+ System.out.println("Round " + round + ":");
+ for (String usedPackage : uses) {
+ System.out.println(" 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 (DEBUG_VIOLATES && !dropped.isEmpty()) {
+ System.out.println();
+ System.out.println("After removal (" + dropped.size() + " dropped)");
+ candidates.dumpResource(targetResource, null, true, System.out);
+ System.out.println();
+ }
+ 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;
+ }
+
+}
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..31487d8f97b 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,19 @@
package org.apache.felix.resolver;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.eclipse.osgi.internal.framework.FilterImpl;
+import org.osgi.framework.Constants;
+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 +68,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);
@@ -81,13 +99,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));
}
@@ -115,4 +133,114 @@ public static List getDynamicRequirements(List reqs)
}
return result;
}
+
+ 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 Collections.emptySet();
+ }
+
+ static String toString(Requirement requirement) {
+ if (PackageNamespace.PACKAGE_NAMESPACE.equals(requirement.getNamespace())) {
+ return Constants.IMPORT_PACKAGE + ": " //$NON-NLS-1$
+ + createOSGiRequirement(requirement, PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE,
+ PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+ } else if (BundleNamespace.BUNDLE_NAMESPACE.equals(requirement.getNamespace())) {
+ return Constants.REQUIRE_BUNDLE + ": " //$NON-NLS-1$
+ + createOSGiRequirement(requirement, BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+ } else if (HostNamespace.HOST_NAMESPACE.equals(requirement.getNamespace())) {
+ return Constants.FRAGMENT_HOST + ": " //$NON-NLS-1$
+ + createOSGiRequirement(requirement, HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+ }
+ return Constants.REQUIRE_CAPABILITY + ": " + requirement.toString(); //$NON-NLS-1$
+ }
+
+ private static String createOSGiRequirement(Requirement requirement, String... versions) {
+ Map directives = new HashMap<>(requirement.getDirectives());
+ String filter = directives.remove(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
+ if (filter == null)
+ throw new IllegalArgumentException("No filter directive found:" + requirement); //$NON-NLS-1$
+ FilterImpl filterImpl;
+ try {
+ filterImpl = FilterImpl.newInstance(filter);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException("Invalid filter directive", e); //$NON-NLS-1$
+ }
+ Map matchingAttributes = filterImpl.getStandardOSGiAttributes(versions);
+ String name = matchingAttributes.remove(requirement.getNamespace());
+ if (name == null)
+ throw new IllegalArgumentException("Invalid requirement: " + requirement); //$NON-NLS-1$
+ return name + toString(matchingAttributes, false, true) + toString(directives, true, true);
+ }
+
+ static String toString(Map map, boolean directives) {
+ return toString(map, directives, false);
+ }
+
+ static String toString(Map map, boolean directives, boolean stringsOnly) {
+ if (map.size() == 0)
+ return ""; //$NON-NLS-1$
+ String assignment = directives ? ":=" : "="; //$NON-NLS-1$ //$NON-NLS-2$
+ Set> set = map.entrySet();
+ StringBuilder sb = new StringBuilder();
+ for (java.util.Map.Entry entry : set) {
+ sb.append("; "); //$NON-NLS-1$
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ if (value instanceof List) {
+ @SuppressWarnings("unchecked")
+ List