blob: bfb1b4681c83da959956fdf43ae279a994ea58ac [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.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.felix.resolver.ResolverImpl.PermutationType;
import org.apache.felix.resolver.ResolverImpl.ResolveSession;
import org.apache.felix.resolver.reason.ReasonException;
import org.apache.felix.resolver.util.*;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.*;
import org.osgi.resource.*;
import org.osgi.service.resolver.HostedCapability;
import org.osgi.service.resolver.ResolutionException;
import org.osgi.service.resolver.ResolveContext;
class Candidates
{
static class PopulateResult {
boolean success;
ResolutionError error;
List<Requirement> remaining;
Map<Requirement, List<Capability>> candidates;
@Override
public String toString() {
return success ? "true" : error != null ? error.getMessage() : "???";
}
}
private final ResolveSession m_session;
// Maps a capability to requirements that match it.
private final OpenHashMapSet<Capability, Requirement> m_dependentMap;
// Maps a requirement to the capability it matches.
private final OpenHashMapList 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 OpenHashMap<Resource, PopulateResult> m_populateResultCache;
private final Map<Capability, Requirement> m_subtitutableMap;
private final OpenHashMapSet<Requirement, Capability> m_delta;
private final AtomicBoolean m_candidateSelectorsUnmodifiable;
/**
* Private copy constructor used by the copy() method.
*/
private Candidates(
ResolveSession session,
AtomicBoolean candidateSelectorsUnmodifiable,
OpenHashMapSet<Capability, Requirement> dependentMap,
OpenHashMapList candidateMap,
Map<Resource, WrappedResource> wrappedHosts,
OpenHashMap<Resource, PopulateResult> populateResultCache,
Map<Capability, Requirement> substitutableMap,
OpenHashMapSet<Requirement, Capability> delta)
{
m_session = session;
m_candidateSelectorsUnmodifiable = candidateSelectorsUnmodifiable;
m_dependentMap = dependentMap;
m_candidateMap = candidateMap;
m_allWrappedHosts = wrappedHosts;
m_populateResultCache = populateResultCache;
m_subtitutableMap = substitutableMap;
m_delta = delta;
}
/**
* Constructs an empty Candidates object.
*/
public Candidates(ResolveSession session)
{
m_session = session;
m_candidateSelectorsUnmodifiable = new AtomicBoolean(false);
m_dependentMap = new OpenHashMapSet<Capability, Requirement>();
m_candidateMap = new OpenHashMapList();
m_allWrappedHosts = new HashMap<Resource, WrappedResource>();
m_populateResultCache = new OpenHashMap<Resource, PopulateResult>();
m_subtitutableMap = new OpenHashMap<Capability, Requirement>();
m_delta = new OpenHashMapSet<Requirement, Capability>(3);
}
public int getNbResources()
{
return m_populateResultCache.size();
}
public Map<Resource, Resource> getRootHosts()
{
Map<Resource, Resource> hosts = new LinkedHashMap<Resource, Resource>();
for (Resource res : m_session.getMandatoryResources())
{
addHost(res, hosts);
}
for (Resource res : m_session.getOptionalResources())
{
if (isPopulated(res)) {
addHost(res, hosts);
}
}
return hosts;
}
private void addHost(Resource res, Map<Resource, Resource> hosts) {
if (res instanceof WrappedResource)
{
res = ((WrappedResource) res).getDeclaredResource();
}
if (!Util.isFragment(res))
{
hosts.put(res, getWrappedHost(res));
} else {
Requirement hostReq = res.getRequirements(HostNamespace.HOST_NAMESPACE).get(0);
Capability hostCap = getFirstCandidate(hostReq);
// If the resource is an already resolved fragment and can not
// be attached to new hosts, there will be no matching host,
// so ignore this resource
if (hostCap != null) {
res = getWrappedHost(hostCap.getResource());
if (res instanceof WrappedResource) {
hosts.put(((WrappedResource) res).getDeclaredResource(), res);
}
}
}
}
/**
* Returns the delta which is the differences in the candidates from the
* original Candidates permutation.
* @return the delta
*/
public Object getDelta()
{
return m_delta;
}
public void populate(Collection<Resource> resources)
{
ResolveContext rc = m_session.getContext();
Set<Resource> toRemove = new HashSet<Resource>();
LinkedList<Resource> toPopulate = new LinkedList<Resource>(resources);
while (!toPopulate.isEmpty())
{
Resource resource = toPopulate.getFirst();
// Get cached result
PopulateResult result = m_populateResultCache.get(resource);
if (result == null)
{
result = new PopulateResult();
result.candidates = new OpenHashMap<Requirement, List<Capability>>();
result.remaining = new ArrayList<Requirement>(resource.getRequirements(null));
m_populateResultCache.put(resource, result);
}
if (result.success || result.error != null)
{
toPopulate.removeFirst();
continue;
}
if (result.remaining.isEmpty())
{
toPopulate.removeFirst();
result.success = true;
addCandidates(result.candidates);
result.candidates = null;
result.remaining = null;
Collection<Resource> relatedResources = rc.findRelatedResources(resource);
m_session.setRelatedResources(resource, relatedResources);
for (Resource relatedResource : relatedResources)
{
if (m_session.isValidRelatedResource(relatedResource))
{
// This resource is a valid related resource;
// populate it now, consider it optional
toPopulate.addFirst(relatedResource);
}
}
continue;
}
// We have a requirement to process
Requirement requirement = result.remaining.remove(0);
if (!isEffective(requirement))
{
continue;
}
List<Capability> candidates = rc.findProviders(requirement);
LinkedList<Resource> newToPopulate = new LinkedList<Resource>();
ResolutionError thrown = processCandidates(newToPopulate, requirement, candidates);
if (candidates.isEmpty() && !Util.isOptional(requirement))
{
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.
result.success = true;
}
else
{
result.error = new MissingRequirementError(requirement, thrown);
toRemove.add(resource);
}
toPopulate.removeFirst();
}
else
{
if (!candidates.isEmpty())
{
result.candidates.put(requirement, candidates);
}
if (!newToPopulate.isEmpty())
{
toPopulate.addAll(0, newToPopulate);
}
}
}
while (!toRemove.isEmpty())
{
Iterator<Resource> iterator = toRemove.iterator();
Resource resource = iterator.next();
iterator.remove();
remove(resource, toRemove);
}
}
private boolean isEffective(Requirement req) {
if (!m_session.getContext().isEffective(req)) {
return false;
}
String res = req.getDirectives().get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
return !PackageNamespace.RESOLUTION_DYNAMIC.equals(res);
}
private void populateSubstitutables()
{
for (Map.Entry<Resource, PopulateResult> populated : m_populateResultCache.fast())
{
if (populated.getValue().success)
{
populateSubstitutables(populated.getKey());
}
}
}
private void populateSubstitutables(Resource resource)
{
// Collect the package names exported
@SuppressWarnings("serial")
OpenHashMap<String, List<Capability>> exportNames = new OpenHashMap<String, List<Capability>>() {
@Override
protected List<Capability> compute(String s) {
return new ArrayList<Capability>(1);
}
};
for (Capability packageExport : resource.getCapabilities(null))
{
if (!PackageNamespace.PACKAGE_NAMESPACE.equals(packageExport.getNamespace()))
{
continue;
}
String packageName = (String) packageExport.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
List<Capability> caps = exportNames.getOrCompute(packageName);
caps.add(packageExport);
}
if (exportNames.isEmpty())
{
return;
}
// Check if any requirements substitute one of the exported packages
for (Requirement req : resource.getRequirements(null))
{
if (!PackageNamespace.PACKAGE_NAMESPACE.equals(req.getNamespace()))
{
continue;
}
CandidateSelector substitutes = m_candidateMap.get(req);
if (substitutes != null && !substitutes.isEmpty())
{
String packageName = (String) substitutes.getCurrentCandidate().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
List<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
if (!exportedPackages.containsAll(substitutes.getRemainingCandidates()))
{
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;
ResolutionError checkSubstitutes()
{
OpenHashMap<Capability, Integer> substituteStatuses = new OpenHashMap<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.fast())
{
// add a permutation that imports a different candidate for the substituted if possible
Requirement substitutedReq = m_subtitutableMap.get(substituteStatus.getKey());
if (substitutedReq != null)
{
m_session.permutateIfNeeded(PermutationType.SUBSTITUTE, substitutedReq, this);
}
Set<Requirement> dependents = m_dependentMap.get(substituteStatus.getKey());
if (dependents != null)
{
for (Requirement dependent : dependents)
{
CandidateSelector candidates = m_candidateMap.get(dependent);
if (candidates != null)
{
candidates:
while (!candidates.isEmpty())
{
Capability candidate = candidates.getCurrentCandidate();
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
candidates.removeCurrentCandidate();
// continue to next candidate
break;
}
}
if (candidates.isEmpty())
{
if (Util.isOptional(dependent))
{
m_candidateMap.remove(dependent);
}
else
{
return new MissingRequirementError(dependent);
}
}
}
}
}
}
return null;
}
private boolean isSubstituted(Capability substitutableCap, Map<Capability, Integer> substituteStatuses)
{
Integer substituteState = substituteStatuses.get(substitutableCap);
if (substituteState == null)
{
return false;
}
switch (substituteState)
{
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
CandidateSelector substitutes = m_candidateMap.get(substitutableReq);
if (substitutes != null)
{
for (Capability substituteCandidate : substitutes.getRemainingCandidates())
{
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 ResolutionError populateDynamic()
{
// Process the candidates, removing any candidates that
// cannot resolve.
// TODO: verify the two following statements
LinkedList<Resource> toPopulate = new LinkedList<Resource>();
ResolutionError rethrow = processCandidates(toPopulate, m_session.getDynamicRequirement(), m_session.getDynamicCandidates());
// Add the dynamic imports candidates.
// Make sure this is done after the call to processCandidates since we want to ensure
// fragment candidates are properly hosted before adding the candidates list which makes a copy
addCandidates(m_session.getDynamicRequirement(), m_session.getDynamicCandidates());
populate(toPopulate);
CandidateSelector caps = m_candidateMap.get(m_session.getDynamicRequirement());
if (caps != null)
{
m_session.getDynamicCandidates().retainAll(caps.getRemainingCandidates());
}
else
{
m_session.getDynamicCandidates().clear();
}
if (m_session.getDynamicCandidates().isEmpty())
{
if (rethrow == null)
{
rethrow = new DynamicImportFailed(m_session.getDynamicRequirement());
}
return rethrow;
}
PopulateResult result = new PopulateResult();
result.success = true;
m_populateResultCache.put(m_session.getDynamicHost(), result);
return null;
}
private ResolutionError processCandidates(
LinkedList<Resource> toPopulate,
Requirement req,
List<Capability> candidates)
{
ResolveContext rc = m_session.getContext();
// Get satisfying candidates and populate their candidates if necessary.
ResolutionError 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);
}
// Do a sanity check incase the resolve context tries to attach
// a fragment to an already resolved host capability
if (HostNamespace.HOST_NAMESPACE.equals(req.getNamespace())) {
if (rc.getWirings().containsKey(candCap.getResource())) {
itCandCap.remove();
continue;
}
}
// 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(req.getResource()))
{
PopulateResult result = m_populateResultCache.get(candCap.getResource());
if (result != null)
{
if (result.error != null)
{
if (rethrow == null)
{
rethrow = result.error;
}
// Remove the candidate since we weren't able to
// populate its candidates.
itCandCap.remove();
}
else if (!result.success)
{
toPopulate.add(candCap.getResource());
}
}
else
{
toPopulate.add(candCap.getResource());
}
}
}
// 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)
{
PopulateResult value = m_populateResultCache.get(resource);
return (value != null && value.success);
}
public ResolutionError getResolutionError(Resource resource)
{
PopulateResult value = m_populateResultCache.get(resource);
return value != null ? value.error : 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 addCandidates(Requirement req, List<Capability> candidates)
{
// Record the candidates.
m_candidateMap.put(req, new CandidateSelector(candidates, m_candidateSelectorsUnmodifiable));
for (Capability cap : candidates)
{
m_dependentMap.getOrCompute(cap).add(req);
}
}
/**
* 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 addCandidates(Map<Requirement, List<Capability>> candidates)
{
for (Map.Entry<Requirement, List<Capability>> entry : candidates.entrySet())
{
addCandidates(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)
{
CandidateSelector candidates = m_candidateMap.get(req);
if (candidates != null)
{
return candidates.getRemainingCandidates();
}
return null;
}
public Capability getFirstCandidate(Requirement req)
{
CandidateSelector candidates = m_candidateMap.get(req);
if (candidates != null && !candidates.isEmpty())
{
return candidates.getCurrentCandidate();
}
return null;
}
public void removeFirstCandidate(Requirement req)
{
CandidateSelector candidates = m_candidateMap.get(req);
// Remove the conflicting candidate.
Capability cap = candidates.removeCurrentCandidate();
if (candidates.isEmpty())
{
m_candidateMap.remove(req);
}
// Update the delta with the removed capability
CopyOnWriteSet<Capability> capPath = m_delta.getOrCompute(req);
capPath.add(cap);
}
public CandidateSelector clearMultipleCardinalityCandidates(Requirement req, Collection<Capability> caps)
{
// this is a special case where we need to completely replace the CandidateSelector
// this method should never be called from normal Candidates permutations
CandidateSelector candidates = m_candidateMap.get(req);
List<Capability> remaining = new ArrayList<Capability>(candidates.getRemainingCandidates());
remaining.removeAll(caps);
candidates = new CandidateSelector(remaining, m_candidateSelectorsUnmodifiable);
m_candidateMap.put(req, candidates);
return candidates;
}
/**
* 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.
*
* @return ResolutionError if the removal of any unselected fragments
* result in the root module being unable to resolve.
*/
public ResolutionError prepare()
{
// 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 =
getHostFragments();
// 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);
CandidateSelector hosts = removeCandidate(hostReq, 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 FragmentNotSelectedError(fragment));
}
// Step 4
// First copy candidates for wrapped requirements to the host.
for (WrappedResource hostResource : hostResources) {
for (Requirement r : hostResource.getRequirements(null))
{
Requirement origReq = ((WrappedRequirement) r).getDeclaredRequirement();
CandidateSelector cands = m_candidateMap.get(origReq);
if (cands != null)
{
if (cands instanceof ShadowList)
{
m_candidateMap.put(r, ShadowList.deepCopy((ShadowList) cands));
} else {
m_candidateMap.put(r, cands.copy());
}
for (Capability cand : cands.getRemainingCandidates())
{
Set<Requirement> dependents = m_dependentMap.get(cand);
dependents.remove(origReq);
dependents.add(r);
}
}
}
}
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.
CopyOnWriteSet<Requirement> dependents = m_dependentMap.get(origCap);
if (dependents != null)
{
dependents = new CopyOnWriteSet<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.
CandidateSelector cands = m_candidateMap.get(r);
ShadowList shadow;
if (!(cands instanceof ShadowList))
{
shadow = ShadowList.createShadowList(cands);
m_candidateMap.put(r, shadow);
cands = shadow;
}
else
{
shadow = (ShadowList) cands;
}
// 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()))
{
shadow.insertHostedCapability(
m_session.getContext(),
(HostedCapability) c,
new SimpleHostedCapability(
hostResource.getDeclaredResource(),
origCap));
}
// If the original capability is from the host, then
// we just need to replace it in the shadow list.
else
{
shadow.replace(origCap, c);
}
}
}
}
}
}
// Lastly, verify that all mandatory revisions are still
// populated, since some might have become unresolved after
// selecting fragments/singletons.
for (Resource resource : m_session.getMandatoryResources())
{
if (!isPopulated(resource))
{
return getResolutionError(resource);
}
}
populateSubstitutables();
m_candidateMap.trim();
m_dependentMap.trim();
// mark the selectors as unmodifiable now
m_candidateSelectorsUnmodifiable.set(true);
return null;
}
// 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>>>> getHostFragments()
{
Map<Capability, Map<String, Map<Version, List<Requirement>>>> hostFragments =
new HashMap<Capability, Map<String, Map<Version, List<Requirement>>>>();
for (Entry<Requirement, CandidateSelector> entry : m_candidateMap.fast())
{
Requirement req = entry.getKey();
CandidateSelector caps = entry.getValue();
for (Capability cap : caps.getRemainingCandidates())
{
// 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>();
if (resVersion == null)
resVersion = new Version(0, 0, 0);
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 resource the module to remove.
* @param ex the resolution error
*/
private void removeResource(Resource resource, ResolutionError ex)
{
// Add removal reason to result cache.
PopulateResult result = m_populateResultCache.get(resource);
result.success = false;
result.error = 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 resource the module to remove.
* @param unresolvedResources 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.
*/
private void remove(Resource resource, Set<Resource> unresolvedResources)
{
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)
{
CandidateSelector candidates = m_candidateMap.remove(req);
if (candidates != null)
{
for (Capability cap : candidates.getRemainingCandidates())
{
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 unresolvedResources 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.
*/
private void remove(Capability c, Set<Resource> unresolvedResources)
{
Set<Requirement> dependents = m_dependentMap.remove(c);
if (dependents != null)
{
for (Requirement r : dependents)
{
CandidateSelector candidates = removeCandidate(r, c);
if (candidates.isEmpty())
{
m_candidateMap.remove(r);
if (!Util.isOptional(r))
{
PopulateResult result = m_populateResultCache.get(r.getResource());
if (result != null)
{
result.success = false;
result.error =
new MissingRequirementError(r, m_populateResultCache.get(c.getResource()).error);
}
unresolvedResources.add(r.getResource());
}
}
}
}
}
private CandidateSelector removeCandidate(Requirement req, Capability cap) {
CandidateSelector candidates = m_candidateMap.get(req);
candidates.remove(cap);
return candidates;
}
/**
* 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()
{
return new Candidates(
m_session,
m_candidateSelectorsUnmodifiable,
m_dependentMap,
m_candidateMap.deepClone(),
m_allWrappedHosts,
m_populateResultCache,
m_subtitutableMap,
m_delta.deepClone());
}
public void dump(ResolveContext rc)
{
// Create set of all revisions from requirements.
Set<Resource> resources = new CopyOnWriteSet<Resource>();
for (Entry<Requirement, CandidateSelector> 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)
{
CandidateSelector candidates = m_candidateMap.get(req);
if ((candidates != null) && (!candidates.isEmpty()))
{
System.out.println(" " + req + ": " + candidates);
}
}
reqs = (wiring != null)
? Util.getDynamicRequirements(wiring.getResourceRequirements(null))
: Util.getDynamicRequirements(resource.getRequirements(null));
for (Requirement req : reqs)
{
CandidateSelector candidates = m_candidateMap.get(req);
if ((candidates != null) && (!candidates.isEmpty()))
{
System.out.println(" " + req + ": " + candidates);
}
}
}
System.out.println("=== END CANDIDATE MAP ===");
}
public Candidates permutate(Requirement req)
{
if (!Util.isMultiple(req) && canRemoveCandidate(req))
{
Candidates perm = copy();
perm.removeFirstCandidate(req);
return perm;
}
return null;
}
public boolean canRemoveCandidate(Requirement req)
{
CandidateSelector candidates = m_candidateMap.get(req);
if (candidates != null)
{
Capability current = candidates.getCurrentCandidate();
if (current != null)
{
// IMPLEMENTATION NOTE:
// Here we check for a req that is used for a substitutable export.
// If we find a substitutable req then an extra check is done to see
// if the substitutable capability is currently depended on as the
// only provider of some other requirement. If it is then we do not
// allow the candidate to be removed.
// This is done because of the way we attempt to reduce permutations
// checked by permuting all used requirements that conflict with a
// directly imported/required capability in one go.
// If we allowed these types of substitutable requirements to move
// to the next capability then the permutation would be thrown out
// because it would cause some other resource to not resolve.
// That in turn would throw out the complete permutation along with
// any follow on permutations that could have resulted.
// See ResolverImpl::checkPackageSpaceConsistency
// Check if the current candidate is substitutable by the req;
// This check is necessary here because of the way we traverse used blames
// allows multiple requirements to be permuted in one Candidates
if (req.equals(m_subtitutableMap.get(current)))
{
// this is a substitute req,
// make sure there is not an existing dependency that would fail if we substitute
Set<Requirement> dependents = m_dependentMap.get(current);
if (dependents != null)
{
for (Requirement dependent : dependents)
{
CandidateSelector dependentSelector = m_candidateMap.get(
dependent);
// If the dependent selector only has one capability left then check if
// the current candidate is the selector's current candidate.
if (dependentSelector != null
&& dependentSelector.getRemainingCandidateCount() <= 1)
{
if (current.equals(
dependentSelector.getCurrentCandidate()))
{
// return false since we do not want to allow this requirement
// to substitute the capability
return false;
}
}
}
}
}
}
return candidates.getRemainingCandidateCount() > 1 || Util.isOptional(req);
}
return false;
}
static class DynamicImportFailed extends ResolutionError {
private final Requirement requirement;
public DynamicImportFailed(Requirement requirement) {
this.requirement = requirement;
}
public String getMessage() {
return "Dynamic import failed.";
}
public Collection<Requirement> getUnresolvedRequirements() {
return Collections.singleton(requirement);
}
@Override
public ResolutionException toException() {
return new ReasonException(ReasonException.Reason.DynamicImport, getMessage(), null, getUnresolvedRequirements());
}
}
static class FragmentNotSelectedError extends ResolutionError {
private final Resource resource;
public FragmentNotSelectedError(Resource resource) {
this.resource = resource;
}
public String getMessage() {
return "Fragment was not selected for attachment: " + resource;
}
@Override
public Collection<Requirement> getUnresolvedRequirements() {
return resource.getRequirements(HostNamespace.HOST_NAMESPACE);
}
@Override
public ResolutionException toException() {
return new ReasonException(ReasonException.Reason.FragmentNotSelected, getMessage(), null, getUnresolvedRequirements());
}
}
static class MissingRequirementError extends ResolutionError {
private final Requirement requirement;
private final ResolutionError cause;
public MissingRequirementError(Requirement requirement) {
this(requirement, null);
}
public MissingRequirementError(Requirement requirement, ResolutionError cause) {
this.requirement = requirement;
this.cause = cause;
}
public String getMessage() {
String msg = "Unable to resolve " + requirement.getResource()
+ ": missing requirement " + requirement;
if (cause != null)
{
msg = msg + " [caused by: " + cause.getMessage() + "]";
}
return msg;
}
public Collection<Requirement> getUnresolvedRequirements() {
return Collections.singleton(requirement);
}
@Override
public ResolutionException toException() {
return new ReasonException(
ReasonException.Reason.MissingRequirement, getMessage(), cause != null ? cause.toException() : null, getUnresolvedRequirements());
}
}
}