Skip to content

Commit

Permalink
Remove all candidates for a substitution package that are mandatory
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
laeubi committed Feb 24, 2024
1 parent f5e48e6 commit 8bcd62a
Show file tree
Hide file tree
Showing 5 changed files with 469 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,41 @@
*/
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.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;
Expand Down Expand Up @@ -710,7 +734,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.
Expand All @@ -722,6 +746,7 @@ public void removeFirstCandidate(Requirement req)
// Update the delta with the removed capability
CopyOnWriteSet<Capability> capPath = m_delta.getOrCompute(req);
capPath.add(cap);
return cap;
}

public CandidateSelector clearMultipleCardinalityCandidates(Requirement req, Collection<Capability> caps)
Expand Down Expand Up @@ -1122,9 +1147,24 @@ private void remove(Capability c, Set<Resource> 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<Capability> 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<Capability> capPath = m_delta.getOrCompute(req);
capPath.add(cap);
}
}
return candidates;
}

Expand All @@ -1147,7 +1187,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<Resource> resources = new CopyOnWriteSet<Resource>();
Expand All @@ -1157,44 +1205,68 @@ 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<Requirement> 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<Requirement> reqs = (wiring != null) ? wiring.getResourceRequirements(null)
: resource.getRequirements(null);
List<Requirement> 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<Requirement> reqs) {
for (Requirement req : reqs) {
CandidateSelector candidates = m_candidateMap.get(req);
if ((candidates != null) && (!candidates.isEmpty())) {
List<Capability> remaining = candidates.getRemainingCandidates();
if (remaining.size() > 1) {
return true;
}
}
}
return false;
}

protected int printRe(List<Requirement> reqs, PrintStream printStream, boolean all) {
int dup = 0;
for (Requirement req : reqs) {
CandidateSelector candidates = m_candidateMap.get(req);
if ((candidates != null) && (!candidates.isEmpty())) {
List<Capability> 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);
return perm;
}
return null;
Expand Down Expand Up @@ -1260,6 +1332,14 @@ public boolean canRemoveCandidate(Requirement req)
return false;
}

Collection<Requirement> getDependent(Capability capability) {
CopyOnWriteSet<Requirement> set = m_dependentMap.get(capability);
if (set == null) {
return Collections.emptySet();
}
return set;
}

static class DynamicImportFailed extends ResolutionError {

private final Requirement requirement;
Expand Down
Loading

0 comments on commit 8bcd62a

Please sign in to comment.