blob: 2588613abfe126e423b5204ce335997496dcfcdf [file] [log] [blame]
/*
* 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.framework.resolver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import org.apache.felix.framework.BundleWiringImpl;
import org.apache.felix.framework.Logger;
import org.apache.felix.framework.capabilityset.CapabilitySet;
import org.apache.felix.framework.util.FelixConstants;
import org.apache.felix.framework.util.Util;
import org.apache.felix.framework.wiring.BundleCapabilityImpl;
import org.apache.felix.framework.wiring.BundleRequirementImpl;
import org.osgi.framework.Constants;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleRequirement;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWire;
public class ResolverImpl implements Resolver
{
private final Logger m_logger;
// Holds candidate permutations based on permutating "uses" chains.
// These permutations are given higher priority.
private final List<Candidates> m_usesPermutations = new ArrayList<Candidates>();
// Holds candidate permutations based on permutating requirement candidates.
// These permutations represent backtracking on previous decisions.
private final List<Candidates> m_importPermutations = new ArrayList<Candidates>();
public ResolverImpl(Logger logger)
{
m_logger = logger;
}
public Map<BundleRevision, List<ResolverWire>> resolve(
ResolverState state,
Set<BundleRevision> mandatoryRevisions,
Set<BundleRevision> optionalRevisions,
Set<BundleRevision> ondemandFragments)
{
Map<BundleRevision, List<ResolverWire>> wireMap =
new HashMap<BundleRevision, List<ResolverWire>>();
Map<BundleRevision, Packages> revisionPkgMap =
new HashMap<BundleRevision, Packages>();
boolean retry;
do
{
retry = false;
try
{
// Create object to hold all candidates.
Candidates allCandidates = new Candidates();
// Populate mandatory revisions; since these are mandatory
// revisions, failure throws a resolve exception.
for (Iterator<BundleRevision> it = mandatoryRevisions.iterator();
it.hasNext(); )
{
BundleRevision br = it.next();
if (Util.isFragment(br) || (br.getWiring() == null))
{
allCandidates.populate(state, br, Candidates.MANDATORY);
}
else
{
it.remove();
}
}
// Populate optional revisions; since these are optional
// revisions, failure does not throw a resolve exception.
for (BundleRevision br : optionalRevisions)
{
boolean isFragment = Util.isFragment(br);
if (isFragment || (br.getWiring() == null))
{
allCandidates.populate(state, br, Candidates.OPTIONAL);
}
}
// Populate ondemand fragments; since these are optional
// revisions, failure does not throw a resolve exception.
for (BundleRevision br : ondemandFragments)
{
boolean isFragment = Util.isFragment(br);
if (isFragment)
{
allCandidates.populate(state, br, Candidates.ON_DEMAND);
}
}
// Merge any fragments into hosts.
allCandidates.prepare();
// Create a combined list of populated revisions; for
// optional revisions. We do not need to consider ondemand
// fragments, since they will only be pulled in if their
// host is already present.
Set<BundleRevision> allRevisions =
new HashSet<BundleRevision>(mandatoryRevisions);
for (BundleRevision br : optionalRevisions)
{
if (allCandidates.isPopulated(br))
{
allRevisions.add(br);
}
}
// Record the initial candidate permutation.
m_usesPermutations.add(allCandidates);
ResolveException rethrow = null;
// If a populated revision is a fragment, then its host
// must ultimately be verified, so store its host requirement
// to use for package space calculation.
Map<BundleRevision, List<BundleRequirement>> hostReqs =
new HashMap<BundleRevision, List<BundleRequirement>>();
for (BundleRevision br : allRevisions)
{
if (Util.isFragment(br))
{
hostReqs.put(
br,
br.getDeclaredRequirements(BundleRevision.HOST_NAMESPACE));
}
}
do
{
rethrow = null;
revisionPkgMap.clear();
m_packageSourcesCache.clear();
allCandidates = (m_usesPermutations.size() > 0)
? m_usesPermutations.remove(0)
: m_importPermutations.remove(0);
//allCandidates.dump();
for (BundleRevision br : allRevisions)
{
BundleRevision target = br;
// If we are resolving a fragment, then get its
// host candidate and verify it instead.
List<BundleRequirement> hostReq = hostReqs.get(br);
if (hostReq != null)
{
target = allCandidates.getCandidates(hostReq.get(0))
.iterator().next().getRevision();
}
calculatePackageSpaces(
allCandidates.getWrappedHost(target), allCandidates, revisionPkgMap,
new HashMap(), new HashSet());
//System.out.println("+++ PACKAGE SPACES START +++");
//dumpRevisionPkgMap(revisionPkgMap);
//System.out.println("+++ PACKAGE SPACES END +++");
try
{
checkPackageSpaceConsistency(
false, allCandidates.getWrappedHost(target),
allCandidates, revisionPkgMap, new HashMap());
}
catch (ResolveException ex)
{
rethrow = ex;
}
}
}
while ((rethrow != null)
&& ((m_usesPermutations.size() > 0) || (m_importPermutations.size() > 0)));
// If there is a resolve exception, then determine if an
// optionally resolved revision is to blame (typically a fragment).
// If so, then remove the optionally resolved resolved and try
// again; otherwise, rethrow the resolve exception.
if (rethrow != null)
{
BundleRevision faultyRevision =
getActualBundleRevision(rethrow.getRevision());
if (rethrow.getRequirement() instanceof HostedRequirement)
{
faultyRevision =
((HostedRequirement) rethrow.getRequirement())
.getOriginalRequirement().getRevision();
}
if (optionalRevisions.remove(faultyRevision))
{
retry = true;
}
else if (ondemandFragments.remove(faultyRevision))
{
retry = true;
}
else
{
throw rethrow;
}
}
// If there is no exception to rethrow, then this was a clean
// resolve, so populate the wire map.
else
{
for (BundleRevision br : allRevisions)
{
BundleRevision target = br;
// If we are resolving a fragment, then we
// actually want to populate its host's wires.
List<BundleRequirement> hostReq = hostReqs.get(br);
if (hostReq != null)
{
target = allCandidates.getCandidates(hostReq.get(0))
.iterator().next().getRevision();
}
if (allCandidates.isPopulated(target))
{
wireMap =
populateWireMap(
allCandidates.getWrappedHost(target),
revisionPkgMap, wireMap, allCandidates);
}
}
}
}
finally
{
// Always clear the state.
m_usesPermutations.clear();
m_importPermutations.clear();
}
}
while (retry);
return wireMap;
}
public Map<BundleRevision, List<ResolverWire>> resolve(
ResolverState state, BundleRevision revision, String pkgName,
Set<BundleRevision> ondemandFragments)
{
// We can only create a dynamic import if the following
// conditions are met:
// 1. The specified revision is resolved.
// 2. The package in question is not already imported.
// 3. The package in question is not accessible via require-bundle.
// 4. The package in question is not exported by the revision.
// 5. The package in question matches a dynamic import of the revision.
// The following call checks all of these conditions and returns
// the associated dynamic import and matching capabilities.
Candidates allCandidates =
getDynamicImportCandidates(state, revision, pkgName);
if (allCandidates != null)
{
Map<BundleRevision, List<ResolverWire>> wireMap = new HashMap<BundleRevision, List<ResolverWire>>();
Map<BundleRevision, Packages> revisionPkgMap = new HashMap<BundleRevision, Packages>();
boolean retry;
do
{
retry = false;
try
{
// Try to populate optional fragments.
for (BundleRevision br : ondemandFragments)
{
if (Util.isFragment(br))
{
allCandidates.populate(state, br, Candidates.ON_DEMAND);
}
}
// Merge any fragments into hosts.
allCandidates.prepare();
// Record the initial candidate permutation.
m_usesPermutations.add(allCandidates);
ResolveException rethrow = null;
do
{
rethrow = null;
revisionPkgMap.clear();
m_packageSourcesCache.clear();
allCandidates = (m_usesPermutations.size() > 0)
? m_usesPermutations.remove(0)
: m_importPermutations.remove(0);
//allCandidates.dump();
// For a dynamic import, the instigating revision
// will never be a fragment since fragments never
// execute code, so we don't need to check for
// this case like we do for a normal resolve.
calculatePackageSpaces(
allCandidates.getWrappedHost(revision), allCandidates, revisionPkgMap,
new HashMap(), new HashSet());
//System.out.println("+++ PACKAGE SPACES START +++");
//dumpRevisionPkgMap(revisionPkgMap);
//System.out.println("+++ PACKAGE SPACES END +++");
try
{
checkPackageSpaceConsistency(
false, allCandidates.getWrappedHost(revision),
allCandidates, revisionPkgMap, new HashMap());
}
catch (ResolveException ex)
{
rethrow = ex;
}
}
while ((rethrow != null)
&& ((m_usesPermutations.size() > 0) || (m_importPermutations.size() > 0)));
// If there is a resolve exception, then determine if an
// optionally resolved revision is to blame (typically a fragment).
// If so, then remove the optionally resolved revision and try
// again; otherwise, rethrow the resolve exception.
if (rethrow != null)
{
BundleRevision faultyRevision =
getActualBundleRevision(rethrow.getRevision());
if (rethrow.getRequirement() instanceof HostedRequirement)
{
faultyRevision =
((HostedRequirement) rethrow.getRequirement())
.getOriginalRequirement().getRevision();
}
if (ondemandFragments.remove(faultyRevision))
{
retry = true;
}
else
{
throw rethrow;
}
}
// If there is no exception to rethrow, then this was a clean
// resolve, so populate the wire map.
else
{
wireMap = populateDynamicWireMap(
revision, pkgName, revisionPkgMap, wireMap, allCandidates);
return wireMap;
}
}
finally
{
// Always clear the state.
m_usesPermutations.clear();
m_importPermutations.clear();
}
}
while (retry);
}
return null;
}
private static Candidates getDynamicImportCandidates(
ResolverState state, BundleRevision revision, String pkgName)
{
// Unresolved revisions cannot dynamically import, nor can the default
// package be dynamically imported.
if ((revision.getWiring() == null) || pkgName.length() == 0)
{
return null;
}
// If the revision doesn't have dynamic imports, then just return
// immediately.
List<BundleRequirement> dynamics =
Util.getDynamicRequirements(revision.getWiring().getRequirements(null));
if ((dynamics == null) || dynamics.isEmpty())
{
return null;
}
// If the revision exports this package, then we cannot
// attempt to dynamically import it.
for (BundleCapability cap : revision.getWiring().getCapabilities(null))
{
if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
&& cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE).equals(pkgName))
{
return null;
}
}
// If this revision already imports or requires this package, then
// we cannot dynamically import it.
if (((BundleWiringImpl) revision.getWiring()).hasPackageSource(pkgName))
{
return null;
}
// Determine if any providers of the package exist.
Map<String, Object> attrs = Collections.singletonMap(
BundleRevision.PACKAGE_NAMESPACE, (Object) pkgName);
BundleRequirementImpl req = new BundleRequirementImpl(
revision,
BundleRevision.PACKAGE_NAMESPACE,
Collections.EMPTY_MAP,
attrs);
SortedSet<BundleCapability> candidates = state.getCandidates(req, false);
// Try to find a dynamic requirement that matches the capabilities.
BundleRequirementImpl dynReq = null;
for (int dynIdx = 0;
(candidates.size() > 0) && (dynReq == null) && (dynIdx < dynamics.size());
dynIdx++)
{
for (Iterator<BundleCapability> itCand = candidates.iterator();
(dynReq == null) && itCand.hasNext(); )
{
BundleCapability cap = itCand.next();
if (CapabilitySet.matches(
(BundleCapabilityImpl) cap,
((BundleRequirementImpl) dynamics.get(dynIdx)).getFilter()))
{
dynReq = (BundleRequirementImpl) dynamics.get(dynIdx);
}
}
}
// If we found a matching dynamic requirement, then filter out
// any candidates that do not match it.
if (dynReq != null)
{
for (Iterator<BundleCapability> itCand = candidates.iterator();
itCand.hasNext(); )
{
BundleCapability cap = itCand.next();
if (!CapabilitySet.matches(
(BundleCapabilityImpl) cap, dynReq.getFilter()))
{
itCand.remove();
}
}
}
else
{
candidates.clear();
}
Candidates allCandidates = null;
if (candidates.size() > 0)
{
allCandidates = new Candidates();
allCandidates.populateDynamic(state, revision, dynReq, candidates);
}
return allCandidates;
}
private void calculatePackageSpaces(
BundleRevision revision,
Candidates allCandidates,
Map<BundleRevision, Packages> revisionPkgMap,
Map<BundleCapability, List<BundleRevision>> usesCycleMap,
Set<BundleRevision> cycle)
{
if (cycle.contains(revision))
{
return;
}
cycle.add(revision);
// Create parallel arrays for requirement and proposed candidate
// capability or actual capability if revision is resolved or not.
List<BundleRequirement> reqs = new ArrayList();
List<BundleCapability> caps = new ArrayList();
boolean isDynamicImporting = false;
if (revision.getWiring() != null)
{
// Use wires to get actual requirements and satisfying capabilities.
for (BundleWire wire : revision.getWiring().getRequiredWires(null))
{
// Wrap the requirement as a hosted requirement if it comes
// from a fragment, since we will need to know the host. We
// also need to wrap if the requirement is a dynamic import,
// since that requirement will be shared with any other
// matching dynamic imports.
BundleRequirement r = wire.getRequirement();
if (!r.getRevision().equals(wire.getRequirerWiring().getRevision())
|| ((r.getDirectives().get(Constants.RESOLUTION_DIRECTIVE) != null)
&& r.getDirectives().get(Constants.RESOLUTION_DIRECTIVE).equals("dynamic")))
{
r = new HostedRequirement(
wire.getRequirerWiring().getRevision(),
(BundleRequirementImpl) r);
}
// Wrap the capability as a hosted capability if it comes
// from a fragment, since we will need to know the host.
BundleCapability c = wire.getCapability();
if (!c.getRevision().equals(wire.getProviderWiring().getRevision()))
{
c = new HostedCapability(
wire.getProviderWiring().getRevision(),
(BundleCapabilityImpl) c);
}
reqs.add(r);
caps.add(c);
}
// Since the revision is resolved, it could be dynamically importing,
// so check to see if there are candidates for any of its dynamic
// imports.
for (BundleRequirement req
: Util.getDynamicRequirements(revision.getWiring().getRequirements(null)))
{
// Get the candidates for the current requirement.
SortedSet<BundleCapability> candCaps =
allCandidates.getCandidates((BundleRequirementImpl) req);
// Optional requirements may not have any candidates.
if (candCaps == null)
{
continue;
}
BundleCapability cap = candCaps.iterator().next();
reqs.add(req);
caps.add(cap);
isDynamicImporting = true;
// Can only dynamically import one at a time, so break
// out of the loop after the first.
break;
}
}
else
{
for (BundleRequirement req : revision.getDeclaredRequirements(null))
{
String resolution = req.getDirectives().get(Constants.RESOLUTION_DIRECTIVE);
if ((resolution == null)
|| !resolution.equals(FelixConstants.RESOLUTION_DYNAMIC))
{
// Get the candidates for the current requirement.
SortedSet<BundleCapability> candCaps =
allCandidates.getCandidates((BundleRequirementImpl) req);
// Optional requirements may not have any candidates.
if (candCaps == null)
{
continue;
}
BundleCapability cap = candCaps.iterator().next();
reqs.add(req);
caps.add(cap);
}
}
}
// First, add all exported packages to the target revision's package space.
calculateExportedPackages(revision, allCandidates, revisionPkgMap);
Packages revisionPkgs = revisionPkgMap.get(revision);
// Second, add all imported packages to the target revision's package space.
for (int i = 0; i < reqs.size(); i++)
{
BundleRequirement req = reqs.get(i);
BundleCapability cap = caps.get(i);
calculateExportedPackages(cap.getRevision(), allCandidates, revisionPkgMap);
mergeCandidatePackages(
revision, req, cap, revisionPkgMap, allCandidates,
new HashMap<BundleRevision, List<BundleCapability>>());
}
// Third, have all candidates to calculate their package spaces.
for (int i = 0; i < caps.size(); i++)
{
calculatePackageSpaces(
caps.get(i).getRevision(), allCandidates, revisionPkgMap,
usesCycleMap, cycle);
}
// Fourth, if the target revision is unresolved or is dynamically importing,
// then add all the uses constraints implied by its imported and required
// packages to its package space.
// NOTE: We do not need to do this for resolved revisions because their
// package space is consistent by definition and these uses constraints
// are only needed to verify the consistency of a resolving revision. The
// only exception is if a resolved revision is dynamically importing, then
// we need to calculate its uses constraints again to make sure the new
// import is consistent with the existing package space.
if ((revision.getWiring() == null) || isDynamicImporting)
{
// Merge uses constraints from required capabilities.
for (int i = 0; i < reqs.size(); i++)
{
BundleRequirement req = reqs.get(i);
BundleCapability cap = caps.get(i);
// Ignore bundle/package requirements, since they are
// considered below.
if (!req.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE)
&& !req.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
List<BundleRequirement> blameReqs = new ArrayList();
blameReqs.add(req);
mergeUses(
revision,
revisionPkgs,
cap,
blameReqs,
revisionPkgMap,
allCandidates,
usesCycleMap);
}
}
// Merge uses constraints from imported packages.
for (Entry<String, List<Blame>> entry : revisionPkgs.m_importedPkgs.entrySet())
{
for (Blame blame : entry.getValue())
{
// Ignore revisions that import from themselves.
if (!blame.m_cap.getRevision().equals(revision))
{
List<BundleRequirement> blameReqs = new ArrayList();
blameReqs.add(blame.m_reqs.get(0));
mergeUses(
revision,
revisionPkgs,
blame.m_cap,
blameReqs,
revisionPkgMap,
allCandidates,
usesCycleMap);
}
}
}
// Merge uses constraints from required bundles.
for (Entry<String, List<Blame>> entry : revisionPkgs.m_requiredPkgs.entrySet())
{
for (Blame blame : entry.getValue())
{
List<BundleRequirement> blameReqs = new ArrayList();
blameReqs.add(blame.m_reqs.get(0));
mergeUses(
revision,
revisionPkgs,
blame.m_cap,
blameReqs,
revisionPkgMap,
allCandidates,
usesCycleMap);
}
}
}
}
private void mergeCandidatePackages(
BundleRevision current, BundleRequirement currentReq, BundleCapability candCap,
Map<BundleRevision, Packages> revisionPkgMap,
Candidates allCandidates, Map<BundleRevision, List<BundleCapability>> cycles)
{
List<BundleCapability> cycleCaps = cycles.get(current);
if (cycleCaps == null)
{
cycleCaps = new ArrayList<BundleCapability>();
cycles.put(current, cycleCaps);
}
if (cycleCaps.contains(candCap))
{
return;
}
cycleCaps.add(candCap);
if (candCap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
mergeCandidatePackage(
current, false, currentReq, candCap, revisionPkgMap);
}
else if (candCap.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE))
{
// TODO: FELIX3 - THIS NEXT LINE IS A HACK. IMPROVE HOW/WHEN WE CALCULATE EXPORTS.
calculateExportedPackages(
candCap.getRevision(), allCandidates, revisionPkgMap);
// Get the candidate's package space to determine which packages
// will be visible to the current revision.
Packages candPkgs = revisionPkgMap.get(candCap.getRevision());
// We have to merge all exported packages from the candidate,
// since the current revision requires it.
for (Entry<String, Blame> entry : candPkgs.m_exportedPkgs.entrySet())
{
mergeCandidatePackage(
current,
true,
currentReq,
entry.getValue().m_cap,
revisionPkgMap);
}
// If the candidate requires any other bundles with reexport visibility,
// then we also need to merge their packages too.
if (candCap.getRevision().getWiring() != null)
{
for (BundleWire bw
: candCap.getRevision().getWiring().getRequiredWires(null))
{
if (bw.getRequirement().getNamespace()
.equals(BundleRevision.BUNDLE_NAMESPACE))
{
String value = bw.getRequirement()
.getDirectives().get(Constants.VISIBILITY_DIRECTIVE);
if ((value != null)
&& value.equals(Constants.VISIBILITY_REEXPORT))
{
mergeCandidatePackages(
current,
currentReq,
bw.getCapability(),
revisionPkgMap,
allCandidates,
cycles);
}
}
}
}
else
{
List<BundleRequirement> reqs = (candCap.getRevision().getWiring() != null)
? candCap.getRevision().getWiring().getRequirements(null)
: candCap.getRevision().getDeclaredRequirements(null);
for (BundleRequirement req
: candCap.getRevision().getDeclaredRequirements(null))
{
if (req.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE))
{
String value =
req.getDirectives().get(Constants.VISIBILITY_DIRECTIVE);
if ((value != null)
&& value.equals(Constants.VISIBILITY_REEXPORT)
&& (allCandidates.getCandidates(req) != null))
{
mergeCandidatePackages(
current,
currentReq,
allCandidates.getCandidates(req).iterator().next(),
revisionPkgMap,
allCandidates,
cycles);
}
}
}
}
}
cycles.remove(current);
}
private void mergeCandidatePackage(
BundleRevision current, boolean requires,
BundleRequirement currentReq, BundleCapability candCap,
Map<BundleRevision, Packages> revisionPkgMap)
{
if (candCap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
// Merge the candidate capability into the revision's package space
// for imported or required packages, appropriately.
String pkgName = (String)
candCap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE);
List blameReqs = new ArrayList();
blameReqs.add(currentReq);
Packages currentPkgs = revisionPkgMap.get(current);
Map<String, List<Blame>> packages = (requires)
? currentPkgs.m_requiredPkgs
: currentPkgs.m_importedPkgs;
List<Blame> blames = packages.get(pkgName);
if (blames == null)
{
blames = new ArrayList<Blame>();
packages.put(pkgName, blames);
}
blames.add(new Blame(candCap, blameReqs));
//dumpRevisionPkgs(current, currentPkgs);
}
}
private void mergeUses(
BundleRevision current, Packages currentPkgs,
BundleCapability mergeCap, List<BundleRequirement> blameReqs,
Map<BundleRevision, Packages> revisionPkgMap,
Candidates allCandidates,
Map<BundleCapability, List<BundleRevision>> cycleMap)
{
// If there are no uses, then just return.
// If the candidate revision is the same as the current revision,
// then we don't need to verify and merge the uses constraints
// since this will happen as we build up the package space.
if (current.equals(mergeCap.getRevision()))
{
return;
}
// Check for cycles.
List<BundleRevision> list = cycleMap.get(mergeCap);
if ((list != null) && list.contains(current))
{
return;
}
list = (list == null) ? new ArrayList<BundleRevision>() : list;
list.add(current);
cycleMap.put(mergeCap, list);
for (BundleCapability candSourceCap : getPackageSources(mergeCap, revisionPkgMap))
{
for (String usedPkgName : ((BundleCapabilityImpl) candSourceCap).getUses())
{
Packages candSourcePkgs = revisionPkgMap.get(candSourceCap.getRevision());
List<Blame> candSourceBlames = null;
// Check to see if the used package is exported.
Blame candExportedBlame = candSourcePkgs.m_exportedPkgs.get(usedPkgName);
if (candExportedBlame != null)
{
candSourceBlames = new ArrayList(1);
candSourceBlames.add(candExportedBlame);
}
else
{
// If the used package is not exported, check to see if it
// is required.
candSourceBlames = candSourcePkgs.m_requiredPkgs.get(usedPkgName);
// Lastly, if the used package is not required, check to see if it
// is imported.
candSourceBlames = (candSourceBlames != null)
? candSourceBlames : candSourcePkgs.m_importedPkgs.get(usedPkgName);
}
// If the used package cannot be found, then just ignore it
// since it has no impact.
if (candSourceBlames == null)
{
continue;
}
List<Blame> usedCaps = currentPkgs.m_usedPkgs.get(usedPkgName);
if (usedCaps == null)
{
usedCaps = new ArrayList<Blame>();
currentPkgs.m_usedPkgs.put(usedPkgName, usedCaps);
}
for (Blame blame : candSourceBlames)
{
if (blame.m_reqs != null)
{
List<BundleRequirement> blameReqs2 = new ArrayList(blameReqs);
blameReqs2.add(blame.m_reqs.get(blame.m_reqs.size() - 1));
usedCaps.add(new Blame(blame.m_cap, blameReqs2));
mergeUses(current, currentPkgs, blame.m_cap, blameReqs2,
revisionPkgMap, allCandidates, cycleMap);
}
else
{
usedCaps.add(new Blame(blame.m_cap, blameReqs));
mergeUses(current, currentPkgs, blame.m_cap, blameReqs,
revisionPkgMap, allCandidates, cycleMap);
}
}
}
}
}
private void checkPackageSpaceConsistency(
boolean isDynamicImporting,
BundleRevision revision,
Candidates allCandidates,
Map<BundleRevision, Packages> revisionPkgMap,
Map<BundleRevision, Object> resultCache)
{
if ((revision.getWiring() != null) && !isDynamicImporting)
{
return;
}
else if(resultCache.containsKey(revision))
{
return;
}
Packages pkgs = revisionPkgMap.get(revision);
ResolveException rethrow = null;
Candidates permutation = null;
Set<BundleRequirement> mutated = null;
// Check for conflicting imports from fragments.
for (Entry<String, List<Blame>> entry : pkgs.m_importedPkgs.entrySet())
{
if (entry.getValue().size() > 1)
{
Blame sourceBlame = null;
for (Blame blame : entry.getValue())
{
if (sourceBlame == null)
{
sourceBlame = blame;
}
else if (!sourceBlame.m_cap.getRevision().equals(blame.m_cap.getRevision()))
{
// Try to permutate the conflicting requirement.
permutate(allCandidates, blame.m_reqs.get(0), m_importPermutations);
// Try to permutate the source requirement.
permutate(allCandidates, sourceBlame.m_reqs.get(0), m_importPermutations);
// Report conflict.
ResolveException ex = new ResolveException(
"Uses constraint violation. Unable to resolve bundle revision "
+ revision.getSymbolicName()
+ " [" + revision
+ "] because it is exposed to package '"
+ entry.getKey()
+ "' from bundle revisions "
+ sourceBlame.m_cap.getRevision().getSymbolicName()
+ " [" + sourceBlame.m_cap.getRevision()
+ "] and "
+ blame.m_cap.getRevision().getSymbolicName()
+ " [" + blame.m_cap.getRevision()
+ "] via two dependency chains.\n\nChain 1:\n"
+ toStringBlame(sourceBlame)
+ "\n\nChain 2:\n"
+ toStringBlame(blame),
revision,
blame.m_reqs.get(0));
m_logger.log(
Logger.LOG_DEBUG,
"Candidate permutation failed due to a conflict with a "
+ "fragment import; will try another if possible.",
ex);
throw ex;
}
}
}
}
for (Entry<String, Blame> entry : pkgs.m_exportedPkgs.entrySet())
{
String pkgName = entry.getKey();
Blame exportBlame = entry.getValue();
if (!pkgs.m_usedPkgs.containsKey(pkgName))
{
continue;
}
for (Blame usedBlame : pkgs.m_usedPkgs.get(pkgName))
{
if (!isCompatible(exportBlame.m_cap, usedBlame.m_cap, revisionPkgMap))
{
// Create a candidate permutation that eliminates all candidates
// that conflict with existing selected candidates.
permutation = (permutation != null)
? permutation
: allCandidates.copy();
rethrow = (rethrow != null)
? rethrow
: new ResolveException(
"Uses constraint violation. Unable to resolve bundle revision "
+ revision.getSymbolicName()
+ " [" + revision
+ "] because it exports package '"
+ pkgName
+ "' and is also exposed to it from bundle revision "
+ usedBlame.m_cap.getRevision().getSymbolicName()
+ " [" + usedBlame.m_cap.getRevision()
+ "] via the following dependency chain:\n\n"
+ toStringBlame(usedBlame),
null,
null);
mutated = (mutated != null)
? mutated
: new HashSet<BundleRequirement>();
for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--)
{
BundleRequirement req = usedBlame.m_reqs.get(reqIdx);
// If we've already permutated this requirement in another
// uses constraint, don't permutate it again just continue
// with the next uses constraint.
if (mutated.contains(req))
{
break;
}
// See if we can permutate the candidates for blamed
// requirement; there may be no candidates if the revision
// associated with the requirement is already resolved.
SortedSet<BundleCapability> candidates =
permutation.getCandidates(req);
if ((candidates != null) && (candidates.size() > 1))
{
mutated.add(req);
Iterator it = candidates.iterator();
it.next();
it.remove();
// Continue with the next uses constraint.
break;
}
}
}
}
if (rethrow != null)
{
if (mutated.size() > 0)
{
m_usesPermutations.add(permutation);
}
m_logger.log(
Logger.LOG_DEBUG,
"Candidate permutation failed due to a conflict between "
+ "an export and import; will try another if possible.",
rethrow);
throw rethrow;
}
}
// Check if there are any uses conflicts with imported packages.
for (Entry<String, List<Blame>> entry : pkgs.m_importedPkgs.entrySet())
{
for (Blame importBlame : entry.getValue())
{
String pkgName = entry.getKey();
if (!pkgs.m_usedPkgs.containsKey(pkgName))
{
continue;
}
for (Blame usedBlame : pkgs.m_usedPkgs.get(pkgName))
{
if (!isCompatible(importBlame.m_cap, usedBlame.m_cap, revisionPkgMap))
{
// Create a candidate permutation that eliminates any candidates
// that conflict with existing selected candidates.
permutation = (permutation != null)
? permutation
: allCandidates.copy();
rethrow = (rethrow != null)
? rethrow
: new ResolveException(
"Uses constraint violation. Unable to resolve bundle revision "
+ revision.getSymbolicName()
+ " [" + revision
+ "] because it is exposed to package '"
+ pkgName
+ "' from bundle revisions "
+ importBlame.m_cap.getRevision().getSymbolicName()
+ " [" + importBlame.m_cap.getRevision()
+ "] and "
+ usedBlame.m_cap.getRevision().getSymbolicName()
+ " [" + usedBlame.m_cap.getRevision()
+ "] via two dependency chains.\n\nChain 1:\n"
+ toStringBlame(importBlame)
+ "\n\nChain 2:\n"
+ toStringBlame(usedBlame),
null,
null);
mutated = (mutated != null)
? mutated
: new HashSet();
for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--)
{
BundleRequirement req = usedBlame.m_reqs.get(reqIdx);
// If we've already permutated this requirement in another
// uses constraint, don't permutate it again just continue
// with the next uses constraint.
if (mutated.contains(req))
{
break;
}
// See if we can permutate the candidates for blamed
// requirement; there may be no candidates if the revision
// associated with the requirement is already resolved.
SortedSet<BundleCapability> candidates =
permutation.getCandidates(req);
if ((candidates != null) && (candidates.size() > 1))
{
mutated.add(req);
Iterator it = candidates.iterator();
it.next();
it.remove();
// Continue with the next uses constraint.
break;
}
}
}
}
// 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 mutated any candidates.
if (mutated.size() > 0)
{
m_usesPermutations.add(permutation);
}
// Try to permutate the candidate for the original
// import requirement; only permutate it if we haven't
// done so already.
BundleRequirement req = importBlame.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.
permutateIfNeeded(allCandidates, req, m_importPermutations);
}
m_logger.log(
Logger.LOG_DEBUG,
"Candidate permutation failed due to a conflict between "
+ "imports; will try another if possible.",
rethrow);
throw rethrow;
}
}
}
resultCache.put(revision, Boolean.TRUE);
// Now check the consistency of all revisions on which the
// current revision 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.
int permCount = m_usesPermutations.size() + m_importPermutations.size();
for (Entry<String, List<Blame>> entry : pkgs.m_importedPkgs.entrySet())
{
for (Blame importBlame : entry.getValue())
{
if (!revision.equals(importBlame.m_cap.getRevision()))
{
try
{
checkPackageSpaceConsistency(
false, importBlame.m_cap.getRevision(),
allCandidates, revisionPkgMap, resultCache);
}
catch (ResolveException ex)
{
// 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 revision
// to backtrack on our current candidate selection.
if (permCount == (m_usesPermutations.size() + m_importPermutations.size()))
{
BundleRequirement req = importBlame.m_reqs.get(0);
permutate(allCandidates, req, m_importPermutations);
}
throw ex;
}
}
}
}
}
private static void permutate(
Candidates allCandidates, BundleRequirement req, List<Candidates> permutations)
{
SortedSet<BundleCapability> candidates = allCandidates.getCandidates(req);
if (candidates.size() > 1)
{
Candidates perm = allCandidates.copy();
candidates = perm.getCandidates(req);
Iterator it = candidates.iterator();
it.next();
it.remove();
permutations.add(perm);
}
}
private static void permutateIfNeeded(
Candidates allCandidates, BundleRequirement req, List<Candidates> permutations)
{
SortedSet<BundleCapability> candidates = allCandidates.getCandidates(req);
if (candidates.size() > 1)
{
// Check existing permutations to make sure we haven't
// already permutated this requirement. This check for
// duplicate permutations is simplistic. It assumes if
// there is any permutation that contains a different
// initial candidate for the requirement in question,
// then it has already been permutated.
boolean permutated = false;
for (Candidates existingPerm : permutations)
{
Set<BundleCapability> existingPermCands = existingPerm.getCandidates(req);
if (!existingPermCands.iterator().next().equals(candidates.iterator().next()))
{
permutated = true;
}
}
// If we haven't already permutated the existing
// import, do so now.
if (!permutated)
{
permutate(allCandidates, req, permutations);
}
}
}
private static void calculateExportedPackages(
BundleRevision revision,
Candidates allCandidates,
Map<BundleRevision, Packages> revisionPkgMap)
{
Packages packages = revisionPkgMap.get(revision);
if (packages != null)
{
return;
}
packages = new Packages(revision);
// Get all exported packages.
List<BundleCapability> caps = (revision.getWiring() != null)
? revision.getWiring().getCapabilities(null)
: revision.getDeclaredCapabilities(null);
Map<String, BundleCapability> exports =
new HashMap<String, BundleCapability>(caps.size());
for (BundleCapability cap : caps)
{
if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
if (!cap.getRevision().equals(revision))
{
cap = new HostedCapability(revision, (BundleCapabilityImpl) cap);
}
exports.put(
(String) cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE),
cap);
}
}
// Remove substitutable exports that were imported.
// For resolved revisions BundleWiring.getCapabilities()
// already excludes imported substitutable exports, but
// for resolving revisions we must look in the candidate
// map to determine which exports are substitutable.
if (!exports.isEmpty())
{
if (revision.getWiring() == null)
{
for (BundleRequirement req : revision.getDeclaredRequirements(null))
{
if (req.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
Set<BundleCapability> cands =
allCandidates.getCandidates((BundleRequirementImpl) req);
if ((cands != null) && !cands.isEmpty())
{
String pkgName = (String) cands.iterator().next()
.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE);
exports.remove(pkgName);
}
}
}
}
// Add all non-substituted exports to the revisions's package space.
for (Entry<String, BundleCapability> entry : exports.entrySet())
{
packages.m_exportedPkgs.put(
entry.getKey(), new Blame(entry.getValue(), null));
}
}
revisionPkgMap.put(revision, packages);
}
private boolean isCompatible(
BundleCapability currentCap, BundleCapability candCap,
Map<BundleRevision, Packages> revisionPkgMap)
{
if ((currentCap != null) && (candCap != null))
{
if (currentCap.equals(candCap))
{
return true;
}
List<BundleCapability> currentSources =
getPackageSources(
currentCap,
revisionPkgMap);
List<BundleCapability> candSources =
getPackageSources(
candCap,
revisionPkgMap);
return currentSources.containsAll(candSources)
|| candSources.containsAll(currentSources);
}
return true;
}
private Map<BundleCapability, List<BundleCapability>> m_packageSourcesCache
= new HashMap();
private List<BundleCapability> getPackageSources(
BundleCapability cap, Map<BundleRevision, Packages> revisionPkgMap)
{
if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
List<BundleCapability> sources = m_packageSourcesCache.get(cap);
if (sources == null)
{
sources = getPackageSourcesInternal(
cap, revisionPkgMap, new ArrayList(), new HashSet());
m_packageSourcesCache.put(cap, sources);
}
return sources;
}
if (!((BundleCapabilityImpl) cap).getUses().isEmpty())
{
List<BundleCapability> caps = new ArrayList<BundleCapability>(1);
caps.add(cap);
return caps;
}
return Collections.EMPTY_LIST;
}
private static List<BundleCapability> getPackageSourcesInternal(
BundleCapability cap, Map<BundleRevision, Packages> revisionPkgMap,
List<BundleCapability> sources, Set<BundleCapability> cycleMap)
{
if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
if (cycleMap.contains(cap))
{
return sources;
}
cycleMap.add(cap);
// Get the package name associated with the capability.
String pkgName = cap.getAttributes()
.get(BundleRevision.PACKAGE_NAMESPACE).toString();
// Since a revision can export the same package more than once, get
// all package capabilities for the specified package name.
List<BundleCapability> caps = (cap.getRevision().getWiring() != null)
? cap.getRevision().getWiring().getCapabilities(null)
: cap.getRevision().getDeclaredCapabilities(null);
for (int capIdx = 0; capIdx < caps.size(); capIdx++)
{
if (caps.get(capIdx).getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
&& caps.get(capIdx).getAttributes().get(BundleRevision.PACKAGE_NAMESPACE).equals(pkgName))
{
sources.add(caps.get(capIdx));
}
}
// Then get any addition sources for the package from required bundles.
Packages pkgs = revisionPkgMap.get(cap.getRevision());
List<Blame> required = pkgs.m_requiredPkgs.get(pkgName);
if (required != null)
{
for (Blame blame : required)
{
getPackageSourcesInternal(blame.m_cap, revisionPkgMap, sources, cycleMap);
}
}
}
return sources;
}
private static BundleRevision getActualBundleRevision(BundleRevision br)
{
if (br instanceof HostBundleRevision)
{
return ((HostBundleRevision) br).getHost();
}
return br;
}
private static BundleCapability getActualCapability(BundleCapability c)
{
if (c instanceof HostedCapability)
{
return ((HostedCapability) c).getOriginalCapability();
}
return c;
}
private static BundleRequirement getActualRequirement(BundleRequirement r)
{
if (r instanceof HostedRequirement)
{
return ((HostedRequirement) r).getOriginalRequirement();
}
return r;
}
private static Map<BundleRevision, List<ResolverWire>> populateWireMap(
BundleRevision revision, Map<BundleRevision, Packages> revisionPkgMap,
Map<BundleRevision, List<ResolverWire>> wireMap,
Candidates allCandidates)
{
BundleRevision unwrappedRevision = getActualBundleRevision(revision);
if ((unwrappedRevision.getWiring() == null)
&& !wireMap.containsKey(unwrappedRevision))
{
wireMap.put(unwrappedRevision, (List<ResolverWire>) Collections.EMPTY_LIST);
List<ResolverWire> packageWires = new ArrayList<ResolverWire>();
List<ResolverWire> bundleWires = new ArrayList<ResolverWire>();
List<ResolverWire> capabilityWires = new ArrayList<ResolverWire>();
for (BundleRequirement req : revision.getDeclaredRequirements(null))
{
SortedSet<BundleCapability> cands = allCandidates.getCandidates(req);
if ((cands != null) && (cands.size() > 0))
{
BundleCapability cand = cands.iterator().next();
// Ignore revisions that import themselves.
if (!revision.equals(cand.getRevision()))
{
if (cand.getRevision().getWiring() == null)
{
populateWireMap(cand.getRevision(),
revisionPkgMap, wireMap, allCandidates);
}
Packages candPkgs = revisionPkgMap.get(cand.getRevision());
ResolverWire wire = new ResolverWireImpl(
unwrappedRevision,
getActualRequirement(req),
getActualBundleRevision(cand.getRevision()),
getActualCapability(cand));
if (req.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
packageWires.add(wire);
}
else if (req.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE))
{
bundleWires.add(wire);
}
else
{
capabilityWires.add(wire);
}
}
}
}
// Combine package wires with require wires last.
packageWires.addAll(bundleWires);
packageWires.addAll(capabilityWires);
wireMap.put(unwrappedRevision, packageWires);
// Add host wire for any fragments.
if (revision instanceof HostBundleRevision)
{
List<BundleRevision> fragments = ((HostBundleRevision) revision).getFragments();
for (BundleRevision fragment : fragments)
{
List<ResolverWire> hostWires = wireMap.get(fragment);
if (hostWires == null)
{
hostWires = new ArrayList<ResolverWire>();
wireMap.put(fragment, hostWires);
}
hostWires.add(
new ResolverWireImpl(
getActualBundleRevision(fragment),
fragment.getDeclaredRequirements(
BundleRevision.HOST_NAMESPACE).get(0),
unwrappedRevision,
unwrappedRevision.getDeclaredCapabilities(
BundleRevision.HOST_NAMESPACE).get(0)));
}
}
}
return wireMap;
}
private static Map<BundleRevision, List<ResolverWire>> populateDynamicWireMap(
BundleRevision revision, String pkgName, Map<BundleRevision, Packages> revisionPkgMap,
Map<BundleRevision, List<ResolverWire>> wireMap, Candidates allCandidates)
{
wireMap.put(revision, (List<ResolverWire>) Collections.EMPTY_LIST);
List<ResolverWire> packageWires = new ArrayList<ResolverWire>();
BundleRequirement dynReq = null;
BundleCapability dynCand = null;
for (BundleRequirement req
: Util.getDynamicRequirements(revision.getWiring().getRequirements(null)))
{
// Get the candidates for the current dynamic requirement.
SortedSet<BundleCapability> candCaps =
allCandidates.getCandidates((BundleRequirementImpl) req);
// Optional requirements may not have any candidates.
if ((candCaps == null) || candCaps.isEmpty())
{
continue;
}
// Record the dynamic requirement.
dynReq = req;
dynCand = candCaps.first();
// Can only dynamically import one at a time, so break
// out of the loop after the first.
break;
}
if (dynReq != null)
{
if (dynCand.getRevision().getWiring() == null)
{
populateWireMap(dynCand.getRevision(), revisionPkgMap, wireMap,
allCandidates);
}
Map<String, Object> attrs = new HashMap(1);
attrs.put(BundleRevision.PACKAGE_NAMESPACE, pkgName);
packageWires.add(
new ResolverWireImpl(
revision,
dynReq,
getActualBundleRevision(dynCand.getRevision()),
getActualCapability(dynCand)));
}
wireMap.put(revision, packageWires);
return wireMap;
}
private static void dumpRevisionPkgMap(Map<BundleRevision, Packages> revisionPkgMap)
{
System.out.println("+++BUNDLE REVISION PKG MAP+++");
for (Entry<BundleRevision, Packages> entry : revisionPkgMap.entrySet())
{
dumpRevisionPkgs(entry.getKey(), entry.getValue());
}
}
private static void dumpRevisionPkgs(BundleRevision revision, Packages packages)
{
System.out.println(revision
+ " (" + ((revision.getWiring() != null) ? "RESOLVED)" : "UNRESOLVED)"));
System.out.println(" EXPORTED");
for (Entry<String, Blame> entry : packages.m_exportedPkgs.entrySet())
{
System.out.println(" " + entry.getKey() + " - " + entry.getValue());
}
System.out.println(" IMPORTED");
for (Entry<String, List<Blame>> entry : packages.m_importedPkgs.entrySet())
{
System.out.println(" " + entry.getKey() + " - " + entry.getValue());
}
System.out.println(" REQUIRED");
for (Entry<String, List<Blame>> entry : packages.m_requiredPkgs.entrySet())
{
System.out.println(" " + entry.getKey() + " - " + entry.getValue());
}
System.out.println(" USED");
for (Entry<String, List<Blame>> entry : packages.m_usedPkgs.entrySet())
{
System.out.println(" " + entry.getKey() + " - " + entry.getValue());
}
}
private static String toStringBlame(Blame blame)
{
StringBuffer sb = new StringBuffer();
if ((blame.m_reqs != null) && !blame.m_reqs.isEmpty())
{
for (int i = 0; i < blame.m_reqs.size(); i++)
{
BundleRequirement req = blame.m_reqs.get(i);
sb.append(" ");
sb.append(req.getRevision().getSymbolicName());
sb.append(" [");
sb.append(req.getRevision().toString());
sb.append("]\n");
if (req.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
sb.append(" import: ");
}
else
{
sb.append(" require: ");
}
sb.append(((BundleRequirementImpl) req).getFilter().toString());
sb.append("\n |");
if (req.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
sb.append("\n export: ");
}
else
{
sb.append("\n provide: ");
}
if ((i + 1) < blame.m_reqs.size())
{
BundleCapability cap = Util.getSatisfyingCapability(
blame.m_reqs.get(i + 1).getRevision(),
(BundleRequirementImpl) blame.m_reqs.get(i));
if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
sb.append(BundleRevision.PACKAGE_NAMESPACE);
sb.append("=");
sb.append(cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE).toString());
BundleCapability usedCap;
if ((i + 2) < blame.m_reqs.size())
{
usedCap = Util.getSatisfyingCapability(
blame.m_reqs.get(i + 2).getRevision(),
(BundleRequirementImpl) blame.m_reqs.get(i + 1));
}
else
{
usedCap = Util.getSatisfyingCapability(
blame.m_cap.getRevision(),
(BundleRequirementImpl) blame.m_reqs.get(i + 1));
}
sb.append("; uses:=");
sb.append(usedCap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE));
}
else
{
sb.append(cap);
}
sb.append("\n");
}
else
{
BundleCapability export = Util.getSatisfyingCapability(
blame.m_cap.getRevision(),
(BundleRequirementImpl) blame.m_reqs.get(i));
sb.append(export.getNamespace());
sb.append("=");
sb.append(export.getAttributes().get(export.getNamespace()).toString());
if (export.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
&& !export.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE)
.equals(blame.m_cap.getAttributes().get(
BundleRevision.PACKAGE_NAMESPACE)))
{
sb.append("; uses:=");
sb.append(blame.m_cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE));
sb.append("\n export: ");
sb.append(BundleRevision.PACKAGE_NAMESPACE);
sb.append("=");
sb.append(blame.m_cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE).toString());
}
sb.append("\n ");
sb.append(blame.m_cap.getRevision().getSymbolicName());
sb.append(" [");
sb.append(blame.m_cap.getRevision().toString());
sb.append("]");
}
}
}
else
{
sb.append(blame.m_cap.getRevision().toString());
}
return sb.toString();
}
private static class Packages
{
private final BundleRevision m_revision;
public final Map<String, Blame> m_exportedPkgs = new HashMap();
public final Map<String, List<Blame>> m_importedPkgs = new HashMap();
public final Map<String, List<Blame>> m_requiredPkgs = new HashMap();
public final Map<String, List<Blame>> m_usedPkgs = new HashMap();
public Packages(BundleRevision revision)
{
m_revision = revision;
}
}
private static class Blame
{
public final BundleCapability m_cap;
public final List<BundleRequirement> m_reqs;
public Blame(BundleCapability cap, List<BundleRequirement> reqs)
{
m_cap = cap;
m_reqs = reqs;
}
@Override
public String toString()
{
return m_cap.getRevision()
+ "." + m_cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE)
+ (((m_reqs == null) || m_reqs.isEmpty())
? " NO BLAME"
: " BLAMED ON " + m_reqs);
}
@Override
public boolean equals(Object o)
{
return (o instanceof Blame) && m_reqs.equals(((Blame) o).m_reqs)
&& m_cap.equals(((Blame) o).m_cap);
}
}
}