blob: 61fa97b9e205819dbf33c39c1abd7e2363bf7cbf [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.resolver;
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.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import org.osgi.framework.Version;
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;
class Candidates
{
public static final int MANDATORY = 0;
public static final int OPTIONAL = 1;
private final Set<Resource> m_mandatoryResources;
// Maps a capability to requirements that match it.
private final Map<Capability, Set<Requirement>> m_dependentMap;
// Maps a requirement to the capability it matches.
private final Map<Requirement, List<Capability>> m_candidateMap;
// Maps a bundle revision to its associated wrapped revision; this only happens
// when a revision being resolved has fragments to attach to it.
private final Map<Resource, WrappedResource> m_allWrappedHosts;
// Map used when populating candidates to hold intermediate and final results.
private final Map<Resource, Object> m_populateResultCache;
// Flag to signal if fragments are present in the candidate map.
private boolean m_fragmentsPresent = false;
private final Map<Resource, Boolean> m_validOnDemandResources;
private final Map<Capability, Requirement> m_subtitutableMap;
/**
* Private copy constructor used by the copy() method.
*
* @param dependentMap the capability dependency map.
* @param candidateMap the requirement candidate map.
* @param hostFragments the fragment map.
* @param wrappedHosts the wrapped hosts map.
* @param substitutableMap
*/
private Candidates(
Set<Resource> mandatoryResources,
Map<Capability, Set<Requirement>> dependentMap,
Map<Requirement, List<Capability>> candidateMap,
Map<Resource, WrappedResource> wrappedHosts, Map<Resource, Object> populateResultCache,
boolean fragmentsPresent,
Map<Resource, Boolean> onDemandResources,
Map<Capability, Requirement> substitutableMap)
{
m_mandatoryResources = mandatoryResources;
m_dependentMap = dependentMap;
m_candidateMap = candidateMap;
m_allWrappedHosts = wrappedHosts;
m_populateResultCache = populateResultCache;
m_fragmentsPresent = fragmentsPresent;
m_validOnDemandResources = onDemandResources;
m_subtitutableMap = substitutableMap;
}
/**
* Constructs an empty Candidates object.
*/
public Candidates(Map<Resource, Boolean> validOnDemandResources)
{
m_mandatoryResources = new HashSet<Resource>();
m_dependentMap = new HashMap<Capability, Set<Requirement>>();
m_candidateMap = new HashMap<Requirement, List<Capability>>();
m_allWrappedHosts = new HashMap<Resource, WrappedResource>();
m_populateResultCache = new HashMap<Resource, Object>();
m_validOnDemandResources = validOnDemandResources;
m_subtitutableMap = new HashMap<Capability, Requirement>();
}
/**
* Populates candidates for the specified revision. How a revision is
* resolved depends on its resolution type as follows:
* <ul>
* <li><tt>MANDATORY</tt> - must resolve and failure to do so throws an
* exception.</li>
* <li><tt>OPTIONAL</tt> - attempt to resolve, but no exception is thrown if
* the resolve fails.</li>
* <li><tt>ON_DEMAND</tt> - only resolve on demand; this only applies to
* fragments and will only resolve a fragment if its host is already
* selected as a candidate.</li>
* </ul>
*
* @param rc the resolve context used for populating the candidates.
* @param resource the resource whose candidates should be populated.
* @param resolution indicates the resolution type.
*/
public final void populate(
ResolveContext rc, Resource resource, int resolution) throws ResolutionException
{
// Get the current result cache value, to make sure the revision
// hasn't already been populated.
Object cacheValue = m_populateResultCache.get(resource);
// Has been unsuccessfully populated.
if (cacheValue instanceof ResolutionException)
{
return;
}
// Has been successfully populated.
else if (cacheValue instanceof Boolean)
{
return;
}
// We will always attempt to populate fragments, since this is necessary
// for ondemand attaching of fragment. However, we'll only attempt to
// populate optional non-fragment revisions if they aren't already
// resolved.
boolean isFragment = Util.isFragment(resource);
if (!isFragment && rc.getWirings().containsKey(resource))
{
return;
}
if (resolution == MANDATORY)
{
m_mandatoryResources.add(resource);
}
try
{
// Try to populate candidates for the optional revision.
populateResource(rc, resource);
}
catch (ResolutionException ex)
{
// Only throw an exception if resolution is mandatory.
if (resolution == MANDATORY)
{
throw ex;
}
}
}
/**
* Populates candidates for the specified revision.
*
* @param state the resolver state used for populating the candidates.
* @param revision the revision whose candidates should be populated.
*/
// TODO: FELIX3 - Modify to not be recursive.
private void populateResource(ResolveContext rc, Resource resource) throws ResolutionException
{
// Determine if we've already calculated this revision's candidates.
// The result cache will have one of three values:
// 1. A resolve exception if we've already attempted to populate the
// revision's candidates but were unsuccessful.
// 2. Boolean.TRUE indicating we've already attempted to populate the
// revision's candidates and were successful.
// 3. An array containing the cycle count, current map of candidates
// for already processed requirements, and a list of remaining
// requirements whose candidates still need to be calculated.
// For case 1, rethrow the exception. For case 2, simply return immediately.
// For case 3, this means we have a cycle so we should continue to populate
// the candidates where we left off and not record any results globally
// until we've popped completely out of the cycle.
// Keeps track of the number of times we've reentered this method
// for the current revision.
Integer cycleCount = null;
// Keeps track of the candidates we've already calculated for the
// current revision's requirements.
Map<Requirement, List<Capability>> localCandidateMap = null;
// Keeps track of the current revision's requirements for which we
// haven't yet found candidates.
List<Requirement> remainingReqs = null;
// Get the cache value for the current revision.
Object cacheValue = m_populateResultCache.get(resource);
// This is case 1.
if (cacheValue instanceof ResolutionException)
{
throw (ResolutionException) cacheValue;
}
// This is case 2.
else if (cacheValue instanceof Boolean)
{
return;
}
// This is case 3.
else if (cacheValue != null)
{
// Increment and get the cycle count.
cycleCount = (Integer) (((Object[]) cacheValue)[0] =
new Integer(((Integer) ((Object[]) cacheValue)[0]).intValue() + 1));
// Get the already populated candidates.
localCandidateMap = (Map) ((Object[]) cacheValue)[1];
// Get the remaining requirements.
remainingReqs = (List) ((Object[]) cacheValue)[2];
}
// If there is no cache value for the current revision, then this is
// the first time we are attempting to populate its candidates, so
// do some one-time checks and initialization.
if ((remainingReqs == null) && (localCandidateMap == null))
{
// Record cycle count.
cycleCount = new Integer(0);
// Create a local map for populating candidates first, just in case
// the revision is not resolvable.
localCandidateMap = new HashMap();
// Create a modifiable list of the revision's requirements.
remainingReqs = new ArrayList(resource.getRequirements(null));
// Add these value to the result cache so we know we are
// in the middle of populating candidates for the current
// revision.
m_populateResultCache.put(resource,
cacheValue = new Object[] { cycleCount, localCandidateMap, remainingReqs });
}
// If we have requirements remaining, then find candidates for them.
while (!remainingReqs.isEmpty())
{
Requirement req = remainingReqs.remove(0);
// Ignore non-effective and dynamic requirements.
String resolution = req.getDirectives()
.get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
if (!rc.isEffective(req)
|| ((resolution != null)
&& resolution.equals(PackageNamespace.RESOLUTION_DYNAMIC)))
{
continue;
}
// Process the candidates, removing any candidates that
// cannot resolve.
List<Capability> candidates = rc.findProviders(req);
ResolutionException rethrow = processCandidates(rc, resource, candidates);
// First, due to cycles, makes sure we haven't already failed in
// a deeper recursion.
Object result = m_populateResultCache.get(resource);
if (result instanceof ResolutionException)
{
throw (ResolutionException) result;
}
// Next, if are no candidates remaining and the requirement is not
// not optional, then record and throw a resolve exception.
else if (candidates.isEmpty() && !Util.isOptional(req))
{
if (Util.isFragment(resource) && rc.getWirings().containsKey(resource))
{
// This is a fragment that is already resolved and there is no unresolved hosts to attach it to.
m_populateResultCache.put(resource, Boolean.TRUE);
return;
}
String msg = "Unable to resolve " + resource
+ ": missing requirement " + req;
if (rethrow != null)
{
msg = msg + " [caused by: " + rethrow.getMessage() + "]";
}
rethrow = new ResolutionException(msg, null, Collections.singleton(req));
m_populateResultCache.put(resource, rethrow);
throw rethrow;
}
// Otherwise, if we actually have candidates for the requirement, then
// add them to the local candidate map.
else if (candidates.size() > 0)
{
localCandidateMap.put(req, candidates);
}
}
// If we are exiting from a cycle then decrement
// cycle counter, otherwise record the result.
if (cycleCount.intValue() > 0)
{
((Object[]) cacheValue)[0] = new Integer(cycleCount.intValue() - 1);
}
else if (cycleCount.intValue() == 0)
{
// Record that the revision was successfully populated.
m_populateResultCache.put(resource, Boolean.TRUE);
// Merge local candidate map into global candidate map.
if (localCandidateMap.size() > 0)
{
add(localCandidateMap);
}
if ((rc instanceof FelixResolveContext) && !Util.isFragment(resource))
{
Collection<Resource> ondemandFragments = ((FelixResolveContext) rc).getOndemandResources(resource);
for (Resource fragment : ondemandFragments)
{
Boolean valid = m_validOnDemandResources.get(fragment);
if (valid == null)
{
// Mark this resource as a valid on demand resource
m_validOnDemandResources.put(fragment, Boolean.TRUE);
valid = Boolean.TRUE;
}
if (valid)
{
// This resource is a valid on demand resource;
// populate it now, consider it optional
populate(rc, fragment, OPTIONAL);
}
}
}
}
}
private void populateSubstitutables()
{
for (Map.Entry<Resource, Object> populated : m_populateResultCache.entrySet())
{
if (populated.getValue() instanceof Boolean)
{
populateSubstitutables(populated.getKey());
}
}
}
private void populateSubstitutables(Resource resource)
{
// Collect the package names exported
List<Capability> packageExports = resource.getCapabilities(PackageNamespace.PACKAGE_NAMESPACE);
if (packageExports.isEmpty())
{
return;
}
List<Requirement> packageImports = resource.getRequirements(PackageNamespace.PACKAGE_NAMESPACE);
if (packageImports.isEmpty())
{
return;
}
Map<String, Collection<Capability>> exportNames = new HashMap<String, Collection<Capability>>();
for (Capability packageExport : packageExports)
{
String packageName = (String) packageExport.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
Collection<Capability> caps = exportNames.get(packageName);
if (caps == null)
{
caps = new ArrayList<Capability>(1);
exportNames.put(packageName, caps);
}
caps.add(packageExport);
}
// Check if any requirements substitute one of the exported packages
for (Requirement req : packageImports)
{
List<Capability> substitutes = m_candidateMap.get(req);
if (substitutes != null && !substitutes.isEmpty())
{
String packageName = (String) substitutes.iterator().next().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
Collection<Capability> exportedPackages = exportNames.get(packageName);
if (exportedPackages != null)
{
// The package is exported;
// Check if the requirement only has the bundle's own export as candidates
substitutes = new ArrayList<Capability>(substitutes);
for (Capability exportedPackage : exportedPackages)
{
substitutes.remove(exportedPackage);
}
if (!substitutes.isEmpty())
{
for (Capability exportedPackage : exportedPackages)
{
m_subtitutableMap.put(exportedPackage, req);
}
}
}
}
}
}
private static final int UNPROCESSED = 0;
private static final int PROCESSING = 1;
private static final int SUBSTITUTED = 2;
private static final int EXPORTED = 3;
void checkSubstitutes(List<Candidates> importPermutations) throws ResolutionException
{
Map<Capability, Integer> substituteStatuses = new HashMap<Capability, Integer>(m_subtitutableMap.size());
for (Capability substitutable : m_subtitutableMap.keySet())
{
// initialize with unprocessed
substituteStatuses.put(substitutable, UNPROCESSED);
}
// note we are iterating over the original unmodified map by design
for (Capability substitutable : m_subtitutableMap.keySet())
{
isSubstituted(substitutable, substituteStatuses);
}
// Remove any substituted exports from candidates
for (Map.Entry<Capability, Integer> substituteStatus : substituteStatuses.entrySet())
{
if (substituteStatus.getValue() == SUBSTITUTED)
{
if (m_dependentMap.isEmpty())
{
// make sure the dependents are populated
populateDependents();
}
}
// add a permutation that imports a different candidate for the substituted if possible
Requirement substitutedReq = m_subtitutableMap.get(substituteStatus.getKey());
if (substitutedReq != null)
{
ResolverImpl.permutateIfNeeded(this, substitutedReq, importPermutations);
}
Set<Requirement> dependents = m_dependentMap.get(substituteStatus.getKey());
if (dependents != null)
{
for (Requirement dependent : dependents)
{
List<Capability> candidates = m_candidateMap.get(dependent);
if (candidates != null)
{
candidates:
for (Iterator<Capability> iCandidates = candidates.iterator(); iCandidates.hasNext();)
{
Capability candidate = iCandidates.next();
Integer candidateStatus = substituteStatuses.get(candidate);
if (candidateStatus == null)
{
candidateStatus = EXPORTED;
}
switch (candidateStatus)
{
case EXPORTED:
// non-substituted candidate hit before the substituted one; do not continue
break candidates;
case SUBSTITUTED:
default:
// Need to remove any substituted that comes before an exported candidate
iCandidates.remove();
// continue to next candidate
break;
}
}
if (candidates.isEmpty())
{
if (Util.isOptional(dependent))
{
clearCandidates(dependent);
}
else
{
String msg = "Unable to resolve " + dependent.getResource()
+ ": missing requirement " + dependent;
throw new ResolutionException(msg, null, Collections.singleton(dependent));
}
}
}
}
}
}
}
private boolean isSubstituted(Capability substitutableCap, Map<Capability, Integer> substituteStatuses)
{
Integer substituteState = substituteStatuses.get(substitutableCap);
if (substituteState == null)
{
return false;
}
switch (substituteState.intValue())
{
case PROCESSING:
// found a cycle mark the initiator as not substituted
substituteStatuses.put(substitutableCap, EXPORTED);
return false;
case SUBSTITUTED:
return true;
case EXPORTED:
return false;
default:
break;
}
Requirement substitutableReq = m_subtitutableMap.get(substitutableCap);
if (substitutableReq == null)
{
// this should never happen.
return false;
}
// mark as processing to detect cycles
substituteStatuses.put(substitutableCap, PROCESSING);
// discover possible substitutes
List<Capability> substitutes = m_candidateMap.get(substitutableReq);
if (substitutes != null)
{
for (Iterator<Capability> iSubstitutes = substitutes.iterator(); iSubstitutes.hasNext();)
{
Capability substituteCandidate = iSubstitutes.next();
if (substituteCandidate.getResource().equals(substitutableCap.getResource()))
{
substituteStatuses.put(substitutableCap, EXPORTED);
return false;
}
if (!isSubstituted(substituteCandidate, substituteStatuses))
{
// The resource's exported package is substituted for this permutation.
substituteStatuses.put(substitutableCap, SUBSTITUTED);
return true;
}
}
}
// if we get here then the export is not substituted
substituteStatuses.put(substitutableCap, EXPORTED);
return false;
}
public void populateDynamic(
ResolveContext rc, Resource resource,
Requirement req, List<Capability> candidates) throws ResolutionException
{
// Record the revision associated with the dynamic require
// as a mandatory revision.
m_mandatoryResources.add(resource);
// Add the dynamic imports candidates.
add(req, candidates);
// Process the candidates, removing any candidates that
// cannot resolve.
ResolutionException rethrow = processCandidates(rc, resource, candidates);
if (candidates.isEmpty())
{
if (rethrow == null)
{
rethrow = new ResolutionException(
"Dynamic import failed.", null, Collections.singleton(req));
}
throw rethrow;
}
m_populateResultCache.put(resource, Boolean.TRUE);
}
/**
* This method performs common processing on the given set of candidates.
* Specifically, it removes any candidates which cannot resolve and it
* synthesizes candidates for any candidates coming from any attached
* fragments, since fragment capabilities only appear once, but technically
* each host represents a unique capability.
*
* @param state the resolver state.
* @param revision the revision being resolved.
* @param candidates the candidates to process.
* @return a resolve exception to be re-thrown, if any, or null.
*/
private ResolutionException processCandidates(
ResolveContext rc,
Resource resource,
List<Capability> candidates)
{
// Get satisfying candidates and populate their candidates if necessary.
ResolutionException rethrow = null;
Set<Capability> fragmentCands = null;
for (Iterator<Capability> itCandCap = candidates.iterator();
itCandCap.hasNext();)
{
Capability candCap = itCandCap.next();
boolean isFragment = Util.isFragment(candCap.getResource());
// If the capability is from a fragment, then record it
// because we have to insert associated host capabilities
// if the fragment is already attached to any hosts.
if (isFragment)
{
if (fragmentCands == null)
{
fragmentCands = new HashSet<Capability>();
}
fragmentCands.add(candCap);
}
// If the candidate revision is a fragment, then always attempt
// to populate candidates for its dependency, since it must be
// attached to a host to be used. Otherwise, if the candidate
// revision is not already resolved and is not the current version
// we are trying to populate, then populate the candidates for
// its dependencies as well.
// NOTE: Technically, we don't have to check to see if the
// candidate revision is equal to the current revision, but this
// saves us from recursing and also simplifies exceptions messages
// since we effectively chain exception messages for each level
// of recursion; thus, any avoided recursion results in fewer
// exceptions to chain when an error does occur.
if ((isFragment || !rc.getWirings().containsKey(candCap.getResource()))
&& !candCap.getResource().equals(resource))
{
try
{
populateResource(rc, candCap.getResource());
}
catch (ResolutionException ex)
{
if (rethrow == null)
{
rethrow = ex;
}
// Remove the candidate since we weren't able to
// populate its candidates.
itCandCap.remove();
}
}
}
// If any of the candidates for the requirement were from a fragment,
// then also insert synthesized hosted capabilities for any other host
// to which the fragment is attached since they are all effectively
// unique capabilities.
if (fragmentCands != null)
{
for (Capability fragCand : fragmentCands)
{
String fragCandName = fragCand.getNamespace();
if (IdentityNamespace.IDENTITY_NAMESPACE.equals(fragCandName))
{
// no need to wrap identity namespace ever
continue;
}
// Only necessary for resolved fragments.
Wiring wiring = rc.getWirings().get(fragCand.getResource());
if (wiring != null)
{
// Fragments only have host wire, so each wire represents
// an attached host.
for (Wire wire : wiring.getRequiredResourceWires(HostNamespace.HOST_NAMESPACE))
{
// If the capability is a package, then make sure the
// host actually provides it in its resolved capabilities,
// since it may be a substitutable export.
if (!fragCandName.equals(PackageNamespace.PACKAGE_NAMESPACE)
|| rc.getWirings().get(wire.getProvider())
.getResourceCapabilities(null).contains(fragCand))
{
// Note that we can just add this as a candidate
// directly, since we know it is already resolved.
// NOTE: We are synthesizing a hosted capability here,
// but we are not using a ShadowList like we do when
// we synthesizing capabilities for unresolved hosts.
// It is not necessary to use the ShadowList here since
// the host is resolved, because in that case we can
// calculate the proper package space by traversing
// the wiring. In the unresolved case, this isn't possible
// so we need to use the ShadowList so we can keep
// a reference to a synthesized resource with attached
// fragments so we can correctly calculate its package
// space.
// Must remove the fragment candidate because we must
// only use hosted capabilities for package namespace
candidates.remove(fragCand);
rc.insertHostedCapability(
candidates,
new WrappedCapability(
wire.getCapability().getResource(),
fragCand));
}
}
}
}
}
return rethrow;
}
public boolean isPopulated(Resource resource)
{
Object value = m_populateResultCache.get(resource);
return ((value != null) && (value instanceof Boolean));
}
public ResolutionException getResolveException(Resource resource)
{
Object value = m_populateResultCache.get(resource);
return ((value != null) && (value instanceof ResolutionException))
? (ResolutionException) value : null;
}
/**
* Adds a requirement and its matching candidates to the internal data
* structure. This method assumes it owns the data being passed in and does
* not make a copy. It takes the data and processes, such as calculating
* which requirements depend on which capabilities and recording any
* fragments it finds for future merging.
*
* @param req the requirement to add.
* @param candidates the candidates matching the requirement.
*/
private void add(Requirement req, List<Capability> candidates)
{
if (req.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
{
m_fragmentsPresent = true;
}
// Record the candidates.
m_candidateMap.put(req, candidates);
}
/**
* Adds requirements and candidates in bulk. The outer map is not retained
* by this method, but the inner data structures are, so they should not be
* further modified by the caller.
*
* @param candidates the bulk requirements and candidates to add.
*/
private void add(Map<Requirement, List<Capability>> candidates)
{
for (Entry<Requirement, List<Capability>> entry : candidates.entrySet())
{
add(entry.getKey(), entry.getValue());
}
}
/**
* Returns the wrapped resource associated with the given resource. If the
* resource was not wrapped, then the resource itself is returned. This is
* really only needed to determine if the root resources of the resolve have
* been wrapped.
*
* @param r the resource whose wrapper is desired.
* @return the wrapper resource or the resource itself if it was not
* wrapped.
*/
public Resource getWrappedHost(Resource r)
{
Resource wrapped = m_allWrappedHosts.get(r);
return (wrapped == null) ? r : wrapped;
}
/**
* Gets the candidates associated with a given requirement.
*
* @param req the requirement whose candidates are desired.
* @return the matching candidates or null.
*/
public List<Capability> getCandidates(Requirement req)
{
return m_candidateMap.get(req);
}
public void clearCandidates(Requirement req)
{
m_candidateMap.remove(req);
}
/**
* Merges fragments into their hosts. It does this by wrapping all host
* modules and attaching their selected fragments, removing all unselected
* fragment modules, and replacing all occurrences of the original fragments
* in the internal data structures with the wrapped host modules instead.
* Thus, fragment capabilities and requirements are merged into the
* appropriate host and the candidates for the fragment now become
* candidates for the host. Likewise, any module depending on a fragment now
* depend on the host. Note that this process is sort of like
* multiplication, since one fragment that can attach to two hosts
* effectively gets multiplied across the two hosts. So, any modules being
* satisfied by the fragment will end up having the two hosts as potential
* candidates, rather than the single fragment.
*
* @throws ResolutionException if the removal of any unselected fragments
* result in the root module being unable to resolve.
*/
public void prepare(ResolveContext rc) throws ResolutionException
{
// Maps a host capability to a map containing its potential fragments;
// the fragment map maps a fragment symbolic name to a map that maps
// a version to a list of fragments requirements matching that symbolic
// name and version.
Map<Capability, Map<String, Map<Version, List<Requirement>>>> hostFragments = Collections.EMPTY_MAP;
if (m_fragmentsPresent)
{
hostFragments = populateDependents();
}
// This method performs the following steps:
// 1. Select the fragments to attach to a given host.
// 2. Wrap hosts and attach fragments.
// 3. Remove any unselected fragments. This is necessary because
// other revisions may depend on the capabilities of unselected
// fragments, so we need to remove the unselected fragments and
// any revisions that depends on them, which could ultimately cause
// the entire resolve to fail.
// 4. Replace all fragments with any host it was merged into
// (effectively multiplying it).
// * This includes setting candidates for attached fragment
// requirements as well as replacing fragment capabilities
// with host's attached fragment capabilities.
// Steps 1 and 2
List<WrappedResource> hostResources = new ArrayList<WrappedResource>();
List<Resource> unselectedFragments = new ArrayList<Resource>();
for (Entry<Capability, Map<String, Map<Version, List<Requirement>>>> hostEntry : hostFragments.entrySet())
{
// Step 1
Capability hostCap = hostEntry.getKey();
Map<String, Map<Version, List<Requirement>>> fragments =
hostEntry.getValue();
List<Resource> selectedFragments = new ArrayList<Resource>();
for (Entry<String, Map<Version, List<Requirement>>> fragEntry
: fragments.entrySet())
{
boolean isFirst = true;
for (Entry<Version, List<Requirement>> versionEntry
: fragEntry.getValue().entrySet())
{
for (Requirement hostReq : versionEntry.getValue())
{
// Selecting the first fragment in each entry, which
// is equivalent to selecting the highest version of
// each fragment with a given symbolic name.
if (isFirst)
{
selectedFragments.add(hostReq.getResource());
isFirst = false;
}
// For any fragment that wasn't selected, remove the
// current host as a potential host for it and remove it
// as a dependent on the host. If there are no more
// potential hosts for the fragment, then mark it as
// unselected for later removal.
else
{
m_dependentMap.get(hostCap).remove(hostReq);
List<Capability> hosts = m_candidateMap.get(hostReq);
hosts.remove(hostCap);
if (hosts.isEmpty())
{
unselectedFragments.add(hostReq.getResource());
}
}
}
}
}
// Step 2
WrappedResource wrappedHost =
new WrappedResource(hostCap.getResource(), selectedFragments);
hostResources.add(wrappedHost);
m_allWrappedHosts.put(hostCap.getResource(), wrappedHost);
}
// Step 3
for (Resource fragment : unselectedFragments)
{
removeResource(fragment,
new ResolutionException(
"Fragment was not selected for attachment: " + fragment));
}
// Step 4
for (WrappedResource hostResource : hostResources)
{
// Replaces capabilities from fragments with the capabilities
// from the merged host.
for (Capability c : hostResource.getCapabilities(null))
{
// Don't replace the host capability, since the fragment will
// really be attached to the original host, not the wrapper.
if (!c.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
{
Capability origCap = ((HostedCapability) c).getDeclaredCapability();
// Note that you might think we could remove the original cap
// from the dependent map, but you can't since it may come from
// a fragment that is attached to multiple hosts, so each host
// will need to make their own copy.
Set<Requirement> dependents = m_dependentMap.get(origCap);
if (dependents != null)
{
dependents = new HashSet<Requirement>(dependents);
m_dependentMap.put(c, dependents);
for (Requirement r : dependents)
{
// We have synthesized hosted capabilities for all
// fragments that have been attached to hosts by
// wrapping the host bundle and their attached
// fragments. We need to use the ResolveContext to
// determine the proper priority order for hosted
// capabilities since the order may depend on the
// declaring host/fragment combination. However,
// internally we completely wrap the host revision
// and make all capabilities/requirements point back
// to the wrapped host not the declaring host. The
// ResolveContext expects HostedCapabilities to point
// to the declaring revision, so we need two separate
// candidate lists: one for the ResolveContext with
// HostedCapabilities pointing back to the declaring
// host and one for the resolver with HostedCapabilities
// pointing back to the wrapped host. We ask the
// ResolveContext to insert its appropriate HostedCapability
// into its list, then we mirror the insert into a
// shadow list with the resolver's HostedCapability.
// We only need to ask the ResolveContext to find
// the insert position for fragment caps since these
// were synthesized and we don't know their priority.
// However, in the resolver's candidate list we need
// to replace all caps with the wrapped caps, no
// matter if they come from the host or fragment,
// since we are completing replacing the declaring
// host and fragments with the wrapped host.
List<Capability> cands = m_candidateMap.get(r);
if (!(cands instanceof ShadowList))
{
ShadowList<Capability> shadow =
new ShadowList<Capability>(cands);
m_candidateMap.put(r, shadow);
cands = shadow;
}
// If the original capability is from a fragment, then
// ask the ResolveContext to insert it and update the
// shadow copy of the list accordingly.
if (!origCap.getResource().equals(hostResource.getDeclaredResource()))
{
List<Capability> original = ((ShadowList) cands).getOriginal();
int removeIdx = original.indexOf(origCap);
if (removeIdx != -1)
{
original.remove(removeIdx);
cands.remove(removeIdx);
}
int insertIdx = rc.insertHostedCapability(
original,
new SimpleHostedCapability(
hostResource.getDeclaredResource(),
origCap));
cands.add(insertIdx, c);
}
// If the original capability is from the host, then
// we just need to replace it in the shadow list.
else
{
int idx = cands.indexOf(origCap);
cands.set(idx, c);
}
}
}
}
}
// Copy candidates for fragment requirements to the host.
for (Requirement r : hostResource.getRequirements(null))
{
Requirement origReq = ((WrappedRequirement) r).getDeclaredRequirement();
List<Capability> cands = m_candidateMap.get(origReq);
if (cands != null)
{
m_candidateMap.put(r, new ArrayList<Capability>(cands));
for (Capability cand : cands)
{
Set<Requirement> dependents = m_dependentMap.get(cand);
dependents.remove(origReq);
dependents.add(r);
}
}
}
}
// Lastly, verify that all mandatory revisions are still
// populated, since some might have become unresolved after
// selecting fragments/singletons.
for (Resource resource : m_mandatoryResources)
{
if (!isPopulated(resource))
{
throw getResolveException(resource);
}
}
populateSubstitutables();
}
// Maps a host capability to a map containing its potential fragments;
// the fragment map maps a fragment symbolic name to a map that maps
// a version to a list of fragments requirements matching that symbolic
// name and version.
private Map<Capability, Map<String, Map<Version, List<Requirement>>>> populateDependents()
{
Map<Capability, Map<String, Map<Version, List<Requirement>>>> hostFragments =
new HashMap<Capability, Map<String, Map<Version, List<Requirement>>>>();
for (Entry<Requirement, List<Capability>> entry : m_candidateMap.entrySet())
{
Requirement req = entry.getKey();
List<Capability> caps = entry.getValue();
for (Capability cap : caps)
{
// Record the requirement as dependent on the capability.
Set<Requirement> dependents = m_dependentMap.get(cap);
if (dependents == null)
{
dependents = new HashSet<Requirement>();
m_dependentMap.put(cap, dependents);
}
dependents.add(req);
// Keep track of hosts and associated fragments.
if (req.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
{
String resSymName = Util.getSymbolicName(req.getResource());
Version resVersion = Util.getVersion(req.getResource());
Map<String, Map<Version, List<Requirement>>> fragments = hostFragments.get(cap);
if (fragments == null)
{
fragments = new HashMap<String, Map<Version, List<Requirement>>>();
hostFragments.put(cap, fragments);
}
Map<Version, List<Requirement>> fragmentVersions = fragments.get(resSymName);
if (fragmentVersions == null)
{
fragmentVersions =
new TreeMap<Version, List<Requirement>>(Collections.reverseOrder());
fragments.put(resSymName, fragmentVersions);
}
List<Requirement> actual = fragmentVersions.get(resVersion);
if (actual == null)
{
actual = new ArrayList<Requirement>();
fragmentVersions.put(resVersion, actual);
}
actual.add(req);
}
}
}
return hostFragments;
}
/**
* Removes a module from the internal data structures if it wasn't selected
* as a fragment or a singleton. This process may cause other modules to
* become unresolved if they depended on the module's capabilities and there
* is no other candidate.
*
* @param revision the module to remove.
* @throws ResolveException if removing the module caused the resolve to
* fail.
*/
private void removeResource(Resource resource, ResolutionException ex)
throws ResolutionException
{
// Add removal reason to result cache.
m_populateResultCache.put(resource, ex);
// Remove from dependents.
Set<Resource> unresolvedResources = new HashSet<Resource>();
remove(resource, unresolvedResources);
// Remove dependents that failed as a result of removing revision.
while (!unresolvedResources.isEmpty())
{
Iterator<Resource> it = unresolvedResources.iterator();
resource = it.next();
it.remove();
remove(resource, unresolvedResources);
}
}
/**
* Removes the specified module from the internal data structures, which
* involves removing its requirements and its capabilities. This may cause
* other modules to become unresolved as a result.
*
* @param br the module to remove.
* @param unresolvedRevisions a list to containing any additional modules
* that that became unresolved as a result of removing this module and will
* also need to be removed.
* @throws ResolveException if removing the module caused the resolve to
* fail.
*/
private void remove(Resource resource, Set<Resource> unresolvedResources)
throws ResolutionException
{
for (Requirement r : resource.getRequirements(null))
{
remove(r);
}
for (Capability c : resource.getCapabilities(null))
{
remove(c, unresolvedResources);
}
}
/**
* Removes a requirement from the internal data structures.
*
* @param req the requirement to remove.
*/
private void remove(Requirement req)
{
boolean isFragment = req.getNamespace().equals(HostNamespace.HOST_NAMESPACE);
List<Capability> candidates = m_candidateMap.remove(req);
if (candidates != null)
{
for (Capability cap : candidates)
{
Set<Requirement> dependents = m_dependentMap.get(cap);
if (dependents != null)
{
dependents.remove(req);
}
}
}
}
/**
* Removes a capability from the internal data structures. This may cause
* other modules to become unresolved as a result.
*
* @param c the capability to remove.
* @param unresolvedRevisions a list to containing any additional modules
* that that became unresolved as a result of removing this module and will
* also need to be removed.
* @throws ResolveException if removing the module caused the resolve to
* fail.
*/
private void remove(Capability c, Set<Resource> unresolvedResources)
throws ResolutionException
{
Set<Requirement> dependents = m_dependentMap.remove(c);
if (dependents != null)
{
for (Requirement r : dependents)
{
List<Capability> candidates = m_candidateMap.get(r);
candidates.remove(c);
if (candidates.isEmpty())
{
m_candidateMap.remove(r);
if (!Util.isOptional(r))
{
String msg = "Unable to resolve " + r.getResource()
+ ": missing requirement " + r;
m_populateResultCache.put(
r.getResource(),
new ResolutionException(msg, null, Collections.singleton(r)));
unresolvedResources.add(r.getResource());
}
}
}
}
}
/**
* Creates a copy of the Candidates object. This is used for creating
* permutations when package space conflicts are discovered.
*
* @return copy of this Candidates object.
*/
public Candidates copy()
{
Map<Capability, Set<Requirement>> dependentMap =
new HashMap<Capability, Set<Requirement>>();
for (Entry<Capability, Set<Requirement>> entry : m_dependentMap.entrySet())
{
Set<Requirement> dependents = new HashSet<Requirement>(entry.getValue());
dependentMap.put(entry.getKey(), dependents);
}
Map<Requirement, List<Capability>> candidateMap =
new HashMap<Requirement, List<Capability>>();
for (Entry<Requirement, List<Capability>> entry
: m_candidateMap.entrySet())
{
List<Capability> candidates =
new ArrayList<Capability>(entry.getValue());
candidateMap.put(entry.getKey(), candidates);
}
return new Candidates(
m_mandatoryResources, dependentMap, candidateMap,
m_allWrappedHosts, m_populateResultCache, m_fragmentsPresent, m_validOnDemandResources,
m_subtitutableMap);
}
public void dump(ResolveContext rc)
{
// Create set of all revisions from requirements.
Set<Resource> resources = new HashSet<Resource>();
for (Entry<Requirement, List<Capability>> entry
: m_candidateMap.entrySet())
{
resources.add(entry.getKey().getResource());
}
// Now dump the revisions.
System.out.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)
{
List<Capability> candidates = m_candidateMap.get(req);
if ((candidates != null) && (candidates.size() > 0))
{
System.out.println(" " + req + ": " + candidates);
}
}
reqs = (wiring != null)
? Util.getDynamicRequirements(wiring.getResourceRequirements(null))
: Util.getDynamicRequirements(resource.getRequirements(null));
for (Requirement req : reqs)
{
List<Capability> candidates = m_candidateMap.get(req);
if ((candidates != null) && (candidates.size() > 0))
{
System.out.println(" " + req + ": " + candidates);
}
}
}
System.out.println("=== END CANDIDATE MAP ===");
}
}