in resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java [1317:1546]
private ResolutionError checkPackageSpaceConsistency(
ResolveSession session,
Resource resource,
Candidates allCandidates,
boolean dynamic,
Map<Resource, Packages> resourcePkgMap,
Map<Resource, Object> resultCache)
{
if (!dynamic && session.getContext().getWirings().containsKey(resource))
{
return null;
}
Object cache = resultCache.get(resource);
if (cache != null)
{
return cache instanceof ResolutionError ? (ResolutionError) cache : null;
}
Packages pkgs = resourcePkgMap.get(resource);
ResolutionError rethrow = null;
// Check for conflicting imports from fragments.
// TODO: Is this only needed for imports or are generic and bundle requirements also needed?
// I think this is only a special case for fragment imports because they can overlap
// host imports, which is not allowed in normal metadata.
for (Entry<String, List<Blame>> entry : pkgs.m_importedPkgs.fast())
{
String pkgName = entry.getKey();
List<Blame> blames = entry.getValue();
if (blames.size() > 1)
{
Blame sourceBlame = null;
for (Blame blame : blames)
{
if (sourceBlame == null)
{
sourceBlame = blame;
}
else if (!sourceBlame.m_cap.getResource().equals(blame.m_cap.getResource()))
{
// Try to permutate the conflicting requirement.
session.addPermutation(PermutationType.IMPORT, allCandidates.permutate(blame.m_reqs.get(0)));
// Try to permutate the source requirement.
session.addPermutation(PermutationType.IMPORT, allCandidates.permutate(sourceBlame.m_reqs.get(0)));
// Report conflict.
rethrow = new UseConstraintError(
session.getContext(), allCandidates,
resource, pkgName,
sourceBlame, blame);
if (m_logger.isDebugEnabled())
{
m_logger.debug(
"Candidate permutation failed due to a conflict with a "
+ "fragment import; will try another if possible."
+ " (" + rethrow.getMessage() + ")");
}
return rethrow;
}
}
}
}
// IMPLEMENTATION NOTE:
// Below we track the mutated reqs that have been permuted
// in a single candidates permutation. This permutation may contain a
// delta of several reqs which conflict with a directly imported/required candidates.
// When several reqs are permuted at the same time this reduces the number of solutions tried.
// See the method Candidates::canRemoveCandidate for a case where substitutions must be checked
// because of this code that may permute multiple reqs in on candidates permutation.
AtomicReference<Candidates> permRef1 = new AtomicReference<Candidates>();
AtomicReference<Candidates> permRef2 = new AtomicReference<Candidates>();
Set<Requirement> mutated = null;
// Check if there are any uses conflicts with exported packages.
for (Entry<String, Blame> entry : pkgs.m_exportedPkgs.fast())
{
String pkgName = entry.getKey();
Blame exportBlame = entry.getValue();
ArrayMap<Set<Capability>, UsedBlames> pkgBlames = pkgs.m_usedPkgs.get(pkgName);
if (pkgBlames == null)
{
continue;
}
for (UsedBlames usedBlames : pkgBlames.values())
{
if (!isCompatible(exportBlame, usedBlames.m_caps, resourcePkgMap))
{
mutated = (mutated != null)
? mutated
: new HashSet<Requirement>();
rethrow = permuteUsedBlames(session, rethrow, allCandidates, resource,
pkgName, null, usedBlames, permRef1, permRef2, mutated);
}
}
if (rethrow != null)
{
if (!mutated.isEmpty())
{
session.addPermutation(PermutationType.USES, permRef1.get());
session.addPermutation(PermutationType.USES, permRef2.get());
}
if (m_logger.isDebugEnabled())
{
m_logger.debug("Candidate permutation failed due to a conflict between "
+ "an export and import; will try another if possible."
+ " (" + rethrow.getMessage() + ")");
}
return rethrow;
}
}
// Check if there are any uses conflicts with imported and required packages.
// We combine the imported and required packages here into one map.
// Imported packages are added after required packages because they shadow or override
// the packages from required bundles.
OpenHashMap<String, List<Blame>> allImportRequirePkgs;
if (pkgs.m_requiredPkgs.isEmpty())
{
allImportRequirePkgs = pkgs.m_importedPkgs;
}
else
{
allImportRequirePkgs = new OpenHashMap<String, List<Blame>>(pkgs.m_requiredPkgs.size() + pkgs.m_importedPkgs.size());
allImportRequirePkgs.putAll(pkgs.m_requiredPkgs);
allImportRequirePkgs.putAll(pkgs.m_importedPkgs);
}
for (Entry<String, List<Blame>> entry : allImportRequirePkgs.fast())
{
String pkgName = entry.getKey();
ArrayMap<Set<Capability>, UsedBlames> pkgBlames = pkgs.m_usedPkgs.get(pkgName);
if (pkgBlames == null)
{
continue;
}
List<Blame> requirementBlames = entry.getValue();
for (UsedBlames usedBlames : pkgBlames.values())
{
if (!isCompatible(requirementBlames, usedBlames.m_caps, resourcePkgMap))
{
mutated = (mutated != null)
? mutated
: new HashSet<Requirement>();// Split packages, need to think how to get a good message for split packages (sigh)
// For now we just use the first requirement that brings in the package that conflicts
Blame requirementBlame = requirementBlames.get(0);
rethrow = permuteUsedBlames(session, rethrow, allCandidates, resource, pkgName, requirementBlame, usedBlames, permRef1, permRef2, mutated);
}
// If there was a uses conflict, then we should add a uses
// permutation if we were able to permutate any candidates.
// Additionally, we should try to push an import permutation
// for the original import to force a backtracking on the
// original candidate decision if no viable candidate is found
// for the conflicting uses constraint.
if (rethrow != null)
{
// Add uses permutation if we m_mutated any candidates.
if (!mutated.isEmpty())
{
session.addPermutation(PermutationType.USES, permRef1.get());
session.addPermutation(PermutationType.USES, permRef2.get());
}
// Try to permutate the candidate for the original
// import requirement; only permutate it if we haven't
// done so already.
for (Blame requirementBlame : requirementBlames)
{
Requirement req = requirementBlame.m_reqs.get(0);
if (!mutated.contains(req))
{
// Since there may be lots of uses constraint violations
// with existing import decisions, we may end up trying
// to permutate the same import a lot of times, so we should
// try to check if that the case and only permutate it once.
session.permutateIfNeeded(PermutationType.IMPORT, req, allCandidates);
}
}
if (m_logger.isDebugEnabled())
{
m_logger.debug("Candidate permutation failed due to a conflict between "
+ "imports; will try another if possible."
+ " (" + rethrow.getMessage() + ")"
);
}
return rethrow;
}
}
}
resultCache.put(resource, Boolean.TRUE);
// Now check the consistency of all resources on which the
// current resource depends. Keep track of the current number
// of permutations so we know if the lower level check was
// able to create a permutation or not in the case of failure.
long permCount = session.getPermutationCount();
for (Requirement req : resource.getRequirements(null))
{
Capability cap = allCandidates.getFirstCandidate(req);
if (cap != null)
{
if (!resource.equals(cap.getResource()))
{
rethrow = checkPackageSpaceConsistency(
session, cap.getResource(),
allCandidates, false, resourcePkgMap, resultCache);
if (session.isCancelled()) {
return null;
}
if (rethrow != null)
{
// If the lower level check didn't create any permutations,
// then we should create an import permutation for the
// requirement with the dependency on the failing resource
// to backtrack on our current candidate selection.
if (permCount == session.getPermutationCount())
{
session.addPermutation(PermutationType.IMPORT, allCandidates.permutate(req));
}
return rethrow;
}
}
}
}
return null;
}