Package org.apache.felix.resolver

Source Code of org.apache.felix.resolver.ResolverImpl$UsedBlames

/*
* 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.StringTokenizer;

import org.osgi.framework.namespace.BundleNamespace;
import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
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.Namespace;
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;
import org.osgi.service.resolver.Resolver;

public class ResolverImpl implements Resolver
{
    private final Logger m_logger;

    // Note this class is not thread safe.
    // Only use in the context of a single thread.
    class ResolveSession
    {
        // Holds the resolve context for this session
        private final ResolveContext m_resolveContext;
        // 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>();
        // Holds candidate permutations based on removing candidates that satisfy
        // multiple cardinality requirements.
        // This permutation represents a permutation that is consistent because we have
        // removed the offending capabilities
        private Candidates m_multipleCardCandidates = null;

        private final Map<Capability, List<Capability>> m_packageSourcesCache = new HashMap();

        ResolveSession(ResolveContext resolveContext)
        {
            m_resolveContext = resolveContext;
        }

        List<Candidates> getUsesPermutations()
        {
            return m_usesPermutations;
        }

        List<Candidates> getImportPermutations()
        {
            return m_importPermutations;
        }

        Candidates getMultipleCardCandidates()
        {
            return m_multipleCardCandidates;
        }

        void setMultipleCardCandidates(Candidates multipleCardCandidates)
        {
            m_multipleCardCandidates = multipleCardCandidates;
        }

        Map<Capability, List<Capability>> getPackageSourcesCache()
        {
            return m_packageSourcesCache;
        }

        ResolveContext getContext()
        {
            return m_resolveContext;
        }
    }

    public ResolverImpl(Logger logger)
    {
        m_logger = logger;
    }

    public Map<Resource, List<Wire>> resolve(ResolveContext rc) throws ResolutionException
    {
        ResolveSession session = new ResolveSession(rc);
        Map<Resource, List<Wire>> wireMap =
            new HashMap<Resource, List<Wire>>();
        Map<Resource, Packages> resourcePkgMap =
            new HashMap<Resource, Packages>();

        // Make copies of arguments in case we want to modify them.
        Collection<Resource> mandatoryResources = new ArrayList(rc.getMandatoryResources());
        Collection<Resource> optionalResources = new ArrayList(rc.getOptionalResources());
        // keeps track of valid on demand fragments that we have seen.
        // a null value or TRUE indicate it is valid
        Map<Resource, Boolean> validOnDemandResources = new HashMap<Resource, Boolean>(0);

        boolean retry;
        do
        {
            retry = false;
            try
            {
                // Create object to hold all candidates.
                Candidates allCandidates = new Candidates(validOnDemandResources);

                // Populate mandatory resources; since these are mandatory
                // resources, failure throws a resolve exception.
                for (Iterator<Resource> it = mandatoryResources.iterator();
                    it.hasNext();)
                {
                    Resource resource = it.next();
                    if (Util.isFragment(resource) || (rc.getWirings().get(resource) == null))
                    {
                        allCandidates.populate(rc, resource, Candidates.MANDATORY);
                    }
                    else
                    {
                        it.remove();
                    }
                }

                // Populate optional resources; since these are optional
                // resources, failure does not throw a resolve exception.
                for (Resource resource : optionalResources)
                {
                    boolean isFragment = Util.isFragment(resource);
                    if (isFragment || (rc.getWirings().get(resource) == null))
                    {
                        allCandidates.populate(rc, resource, Candidates.OPTIONAL);
                    }
                }

                // Merge any fragments into hosts.
                allCandidates.prepare(rc);

                // Create a combined list of populated resources; for
                // optional resources. We do not need to consider ondemand
                // fragments, since they will only be pulled in if their
                // host is already present.
                Set<Resource> allResources =
                    new HashSet<Resource>(mandatoryResources);
                for (Resource resource : optionalResources)
                {
                    if (allCandidates.isPopulated(resource))
                    {
                        allResources.add(resource);
                    }
                }

                List<Candidates> usesPermutations = session.getUsesPermutations();
                List<Candidates> importPermutations = session.getImportPermutations();

                // Record the initial candidate permutation.
                usesPermutations.add(allCandidates);

                ResolutionException rethrow = null;

                // If a populated resource is a fragment, then its host
                // must ultimately be verified, so store its host requirement
                // to use for package space calculation.
                Map<Resource, List<Requirement>> hostReqs =
                    new HashMap<Resource, List<Requirement>>();
                for (Resource resource : allResources)
                {
                    if (Util.isFragment(resource))
                    {
                        hostReqs.put(
                            resource,
                            resource.getRequirements(HostNamespace.HOST_NAMESPACE));
                    }
                }

                Map<Resource, ResolutionException> faultyResources = null;
                do
                {
                    rethrow = null;

                    resourcePkgMap.clear();
                    session.getPackageSourcesCache().clear();
                    // Null out each time a new permutation is attempted.
                    // We only use this to store a valid permutation which is a
                    // delta of the current permutation.
                    session.setMultipleCardCandidates(null);

                    allCandidates = (usesPermutations.size() > 0)
                        ? usesPermutations.remove(0)
                        : importPermutations.remove(0);
//allCandidates.dump();

                    Map<Resource, ResolutionException> currentFaultyResources = null;
                    try
                    {
                        allCandidates.checkSubstitutes(importPermutations);
                    }
                    catch (ResolutionException e)
                    {
                        rethrow = e;
                        continue;
                    }

                    // Reuse a resultCache map for checking package consistency
                    // for all resources.
                    Map<Resource, Object> resultCache =
                        new HashMap<Resource, Object>(allResources.size());
                    // Check the package space consistency for all 'root' resources.
                    for (Resource resource : allResources)
                    {
                        Resource target = resource;

                        // If we are resolving a fragment, then get its
                        // host candidate and verify it instead.
                        List<Requirement> hostReq = hostReqs.get(resource);
                        if (hostReq != null)
                        {
                            target = allCandidates.getCandidates(hostReq.get(0))
                                .iterator().next().getResource();
                        }

                        calculatePackageSpaces(
                            session, allCandidates.getWrappedHost(target), allCandidates,
                            resourcePkgMap, new HashMap(), new HashSet());
//System.out.println("+++ PACKAGE SPACES START +++");
//dumpResourcePkgMap(resourcePkgMap);
//System.out.println("+++ PACKAGE SPACES END +++");

                        try
                        {
                            checkPackageSpaceConsistency(
                                session, allCandidates.getWrappedHost(target),
                                allCandidates, resourcePkgMap, resultCache);
                        }
                        catch (ResolutionException ex)
                        {
                            rethrow = ex;
                            if (currentFaultyResources == null)
                            {
                                currentFaultyResources = new HashMap<Resource, ResolutionException>();
                            }
                            Resource faultyResource = resource;
                            // check that the faulty requirement is not from a fragment
                            for (Requirement faultyReq : ex.getUnresolvedRequirements())
                            {
                                if (faultyReq instanceof WrappedRequirement)
                                {
                                    faultyResource =
                                        ((WrappedRequirement) faultyReq)
                                        .getDeclaredRequirement().getResource();
                                    break;
                                }
                            }
                            currentFaultyResources.put(faultyResource, ex);
                        }
                    }
                    if (currentFaultyResources != null)
                    {
                        if (faultyResources == null)
                        {
                            faultyResources = currentFaultyResources;
                        }
                        else if (faultyResources.size() > currentFaultyResources.size())
                        {
                            // save the optimal faultyResources which has less
                            faultyResources = currentFaultyResources;
                        }
                    }
                }
                while ((rethrow != null)
                    && ((usesPermutations.size() > 0) || (importPermutations.size() > 0)));

                // If there is a resolve exception, then determine if an
                // optionally resolved resource 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)
                {
                    if (faultyResources != null)
                    {
                        Set<Resource> resourceKeys = faultyResources.keySet();
                        retry = (optionalResources.removeAll(resourceKeys));
                        for (Resource faultyResource : resourceKeys)
                        {
                            Boolean valid = validOnDemandResources.get(faultyResource);
                            if (valid != null && valid.booleanValue())
                            {
                                // This was an ondemand resource.
                                // Invalidate it and try again.
                                validOnDemandResources.put(faultyResource, Boolean.FALSE);
                                retry = true;
                            }
                        }
                        // log all the resolution exceptions for the uses constraint violations
                        for (Map.Entry<Resource, ResolutionException> usesError : faultyResources.entrySet())
                        {
                            m_logger.logUsesConstraintViolation(usesError.getKey(), usesError.getValue());
                        }
                    }
                    if (!retry)
                    {
                        throw rethrow;
                    }
                }
                // If there is no exception to rethrow, then this was a clean
                // resolve, so populate the wire map.
                else
                {
                    if (session.getMultipleCardCandidates() != null)
                    {
                        // Candidates for multiple cardinality requirements were
                        // removed in order to provide a consistent class space.
                        // Use the consistent permutation
                        allCandidates = session.getMultipleCardCandidates();
                    }
                    for (Resource resource : allResources)
                    {
                        Resource target = resource;

                        // If we are resolving a fragment, then we
                        // actually want to populate its host's wires.
                        List<Requirement> hostReq = hostReqs.get(resource);
                        if (hostReq != null)
                        {
                            target = allCandidates.getCandidates(hostReq.get(0))
                                .iterator().next().getResource();
                        }

                        if (allCandidates.isPopulated(target))
                        {
                            wireMap =
                                populateWireMap(
                                    rc, allCandidates.getWrappedHost(target),
                                    resourcePkgMap, wireMap, allCandidates);
                        }
                    }
                }
            }
            finally
            {
                // Always clear the state.
                session.getUsesPermutations().clear();
                session.getImportPermutations().clear();
                session.setMultipleCardCandidates(null);
                // TODO this was not cleared out before; but it seems it should be
                session.getPackageSourcesCache().clear();
            }
        }
        while (retry);

        return wireMap;
    }

    /**
     * Resolves a dynamic requirement for the specified host resource using the
     * specified {@link ResolveContext}. The dynamic requirement may contain
     * wild cards in its filter for the package name. The matching candidates
     * are used to resolve the requirement and the resolve context is not asked
     * to find providers for the dynamic requirement. The host resource is
     * expected to not be a fragment, to already be resolved and have an
     * existing wiring provided by the resolve context.
     * <p>
     * This operation may resolve additional resources in order to resolve the
     * dynamic requirement. The returned map will contain entries for each
     * resource that got resolved in addition to the specified host resource.
     * The wire list for the host resource will only contain a single wire which
     * is for the dynamic requirement.
     *
     * @param rc the resolve context
     * @param host the hosting resource
     * @param dynamicReq the dynamic requirement
     * @param matches a list of matching capabilities
     * @return The new resources and wires required to satisfy the specified
     * dynamic requirement. The returned map is the property of the caller and
     * can be modified by the caller.
     * @throws ResolutionException
     */
    public Map<Resource, List<Wire>> resolve(
        ResolveContext rc, Resource host, Requirement dynamicReq,
        List<Capability> matches)
        throws ResolutionException
    {
        ResolveSession session = new ResolveSession(rc);
        Map<Resource, List<Wire>> wireMap = new HashMap<Resource, List<Wire>>();

        // We can only create a dynamic import if the following
        // conditions are met:
        // 1. The specified resource 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 resource.
        // 5. The package in question matches a dynamic import of the resource.
        if (!matches.isEmpty() && rc.getWirings().containsKey(host))
        {
            // Make sure all matching candidates are packages.
            for (Capability cap : matches)
            {
                if (!cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
                {
                    throw new IllegalArgumentException(
                        "Matching candidate does not provide a package name.");
                }
            }

            Map<Resource, Packages> resourcePkgMap = new HashMap<Resource, Packages>();
            Map<Resource, Boolean> onDemandResources = new HashMap<Resource, Boolean>();

            boolean retry;
            do
            {
                retry = false;

                try
                {
                    // Create all candidates pre-populated with the single candidate set
                    // for the resolving dynamic import of the host.
                    Candidates allCandidates = new Candidates(onDemandResources);
                    allCandidates.populateDynamic(rc, host, dynamicReq, matches);
                    // Merge any fragments into hosts.
                    allCandidates.prepare(rc);

                    List<Candidates> usesPermutations = session.getUsesPermutations();
                    List<Candidates> importPermutations = session.getImportPermutations();

                    // Record the initial candidate permutation.
                    usesPermutations.add(allCandidates);

                    ResolutionException rethrow = null;

                    do
                    {
                        rethrow = null;

                        resourcePkgMap.clear();
                        session.getPackageSourcesCache().clear();

                        allCandidates = (usesPermutations.size() > 0)
                            ? usesPermutations.remove(0)
                            : importPermutations.remove(0);
//allCandidates.dump();

                        try
                        {
                            allCandidates.checkSubstitutes(importPermutations);
                        }
                        catch (ResolutionException e)
                        {
                            rethrow = e;
                            continue;
                        }
                        // For a dynamic import, the instigating resource
                        // 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(session,
                            allCandidates.getWrappedHost(host), allCandidates,
                            resourcePkgMap, new HashMap(), new HashSet());
//System.out.println("+++ PACKAGE SPACES START +++");
//dumpResourcePkgMap(resourcePkgMap);
//System.out.println("+++ PACKAGE SPACES END +++");

                        try
                        {
                            checkDynamicPackageSpaceConsistency(session,
                                allCandidates.getWrappedHost(host),
                                allCandidates, resourcePkgMap, new HashMap());
                        }
                        catch (ResolutionException ex)
                        {
                            rethrow = ex;
                        }
                    }
                    while ((rethrow != null)
                        && ((usesPermutations.size() > 0) || (importPermutations.size() > 0)));

                    // If there is a resolve exception, then determine if an
                    // optionally resolved resource is to blame (typically a fragment).
                    // If so, then remove the optionally resolved resource and try
                    // again; otherwise, rethrow the resolve exception.
                    if (rethrow != null)
                    {
                        Collection<Requirement> exReqs = rethrow.getUnresolvedRequirements();
                        Requirement faultyReq = ((exReqs == null) || (exReqs.isEmpty()))
                            ? null : exReqs.iterator().next();
                        Resource faultyResource = (faultyReq == null)
                            ? null : getDeclaredResource(faultyReq.getResource());
                        // If the faulty requirement is wrapped, then it may
                        // be from a fragment, so consider the fragment faulty
                        // instead of the host.
                        if (faultyReq instanceof WrappedRequirement)
                        {
                            faultyResource =
                                ((WrappedRequirement) faultyReq)
                                .getDeclaredRequirement().getResource();
                        }
                        Boolean valid = onDemandResources.get(faultyResource);
                        if (valid != null && valid.booleanValue())
                        {
                            onDemandResources.put(faultyResource, Boolean.FALSE);
                            retry = true;
                        }
                        else
                        {
                            throw rethrow;
                        }
                    }
                    // If there is no exception to rethrow, then this was a clean
                    // resolve, so populate the wire map.
                    else
                    {
                        if (session.getMultipleCardCandidates() != null)
                        {
                            // TODO this was not done before; but I think it should be;
                            // Candidates for multiple cardinality requirements were
                            // removed in order to provide a consistent class space.
                            // Use the consistent permutation
                            allCandidates = session.getMultipleCardCandidates();
                        }
                        wireMap = populateDynamicWireMap(rc,
                            host, dynamicReq, resourcePkgMap, wireMap, allCandidates);
                    }
                }
                finally
                {
                    // Always clear the state.
                    session.getUsesPermutations().clear();
                    session.getImportPermutations().clear();
                    // TODO these were not cleared out before; but it seems they should be
                    session.setMultipleCardCandidates(null);
                    session.getPackageSourcesCache().clear();
                }
            }
            while (retry);
        }

        return wireMap;
    }

    private void calculatePackageSpaces(
        ResolveSession session,
        Resource resource,
        Candidates allCandidates,
        Map<Resource, Packages> resourcePkgMap,
        Map<Capability, List<Resource>> usesCycleMap,
        Set<Resource> cycle)
    {
        if (cycle.contains(resource))
        {
            return;
        }
        cycle.add(resource);

        // Make sure package space hasn't already been calculated.
        Packages resourcePkgs = resourcePkgMap.get(resource);
        if (resourcePkgs != null)
        {
            if (resourcePkgs.m_isCalculated)
            {
                return;
            }
            else
            {
                resourcePkgs.m_isCalculated = true;
            }
        }

        // Create parallel lists for requirement and proposed candidate
        // capability or actual capability if resource is resolved or not.
        // We use parallel lists so we can calculate the packages spaces for
        // resolved and unresolved resources in an identical fashion.
        List<Requirement> reqs = new ArrayList();
        List<Capability> caps = new ArrayList();
        boolean isDynamicImporting = false;
        Wiring wiring = session.getContext().getWirings().get(resource);
        if (wiring != null)
        {
            // Use wires to get actual requirements and satisfying capabilities.
            for (Wire wire : wiring.getRequiredResourceWires(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.
                Requirement r = wire.getRequirement();
                if (!r.getResource().equals(wire.getRequirer())
                    || ((r.getDirectives()
                    .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE) != null)
                    && r.getDirectives()
                    .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE)
                    .equals(PackageNamespace.RESOLUTION_DYNAMIC)))
                {
                    r = new WrappedRequirement(wire.getRequirer(), r);
                }
                // Wrap the capability as a hosted capability if it comes
                // from a fragment, since we will need to know the host.
                Capability c = wire.getCapability();
                if (!c.getResource().equals(wire.getProvider()))
                {
                    c = new WrappedCapability(wire.getProvider(), c);
                }
                reqs.add(r);
                caps.add(c);
            }

            // Since the resource is resolved, it could be dynamically importing,
            // so check to see if there are candidates for any of its dynamic
            // imports.
            //
            // NOTE: If the resource is dynamically importing, the fact that
            // the dynamic import is added here last to the parallel reqs/caps
            // list is used later when checking to see if the package being
            // dynamically imported shadows an existing provider.
            for (Requirement req
                : Util.getDynamicRequirements(wiring.getResourceRequirements(null)))
            {
                // Get the candidates for the current requirement.
                List<Capability> candCaps = allCandidates.getCandidates(req);
                // Optional requirements may not have any candidates.
                if (candCaps == null)
                {
                    continue;
                }
                // Grab first (i.e., highest priority) candidate.
                Capability cap = candCaps.get(0);
                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 (Requirement req : resource.getRequirements(null))
            {
                if (!Util.isDynamic(req))
                {
                    // Get the candidates for the current requirement.
                    List<Capability> candCaps = allCandidates.getCandidates(req);
                    // Optional requirements may not have any candidates.
                    if (candCaps == null)
                    {
                        continue;
                    }

                    // For multiple cardinality requirements, we need to grab
                    // all candidates.
                    if (Util.isMultiple(req))
                    {
                        // Use the same requirement, but list each capability separately
                        for (Capability cap : candCaps)
                        {
                            reqs.add(req);
                            caps.add(cap);
                        }
                    }
                    // Grab first (i.e., highest priority) candidate
                    else
                    {
                        Capability cap = candCaps.get(0);
                        reqs.add(req);
                        caps.add(cap);
                    }
                }
            }
        }

        // First, add all exported packages to the target resource's package space.
        calculateExportedPackages(session.getContext(), resource, allCandidates, resourcePkgMap);
        resourcePkgs = resourcePkgMap.get(resource);

        // Second, add all imported packages to the target resource's package space.
        for (int i = 0; i < reqs.size(); i++)
        {
            Requirement req = reqs.get(i);
            Capability cap = caps.get(i);
            calculateExportedPackages(
                session.getContext(), cap.getResource(), allCandidates, resourcePkgMap);

            // If this resource is dynamically importing, then the last requirement
            // is the dynamic import being resolved, since it is added last to the
            // parallel lists above. For the dynamically imported package, make
            // sure that the resource doesn't already have a provider for that
            // package, which would be illegal and shouldn't be allowed.
            if (isDynamicImporting && ((i + 1) == reqs.size()))
            {
                String pkgName = (String) cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
                if (resourcePkgs.m_exportedPkgs.containsKey(pkgName)
                    || resourcePkgs.m_importedPkgs.containsKey(pkgName)
                    || resourcePkgs.m_requiredPkgs.containsKey(pkgName))
                {
                    throw new IllegalArgumentException(
                        "Resource "
                        + resource
                        + " cannot dynamically import package '"
                        + pkgName
                        + "' since it already has access to it.");
                }
            }

            mergeCandidatePackages(
                session.getContext(), resource, req, cap, resourcePkgMap, allCandidates,
                new HashMap<Resource, List<Capability>>());
        }

        // Third, have all candidates to calculate their package spaces.
        for (int i = 0; i < caps.size(); i++)
        {
            calculatePackageSpaces(
                session, caps.get(i).getResource(), allCandidates, resourcePkgMap,
                usesCycleMap, cycle);
        }

        // Fourth, if the target resource 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 resources because their
        // package space is consistent by definition and these uses constraints
        // are only needed to verify the consistency of a resolving resource. The
        // only exception is if a resolved resource 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 ((wiring == null) || isDynamicImporting)
        {
            // Merge uses constraints from required capabilities.
            for (int i = 0; i < reqs.size(); i++)
            {
                Requirement req = reqs.get(i);
                Capability cap = caps.get(i);
                // Ignore bundle/package requirements, since they are
                // considered below.
                if (!req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE)
                    && !req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
                {
                    List<Requirement> blameReqs = new ArrayList<Requirement>();
                    blameReqs.add(req);

                    mergeUses(
                        session,
                        resource,
                        resourcePkgs,
                        cap,
                        blameReqs,
                        cap,
                        resourcePkgMap,
                        allCandidates,
                        usesCycleMap);
                }
            }
            // Merge uses constraints from imported packages.
            for (Entry<String, List<Blame>> entry : resourcePkgs.m_importedPkgs.entrySet())
            {
                for (Blame blame : entry.getValue())
                {
                    // Ignore resources that import from themselves.
                    if (!blame.m_cap.getResource().equals(resource))
                    {
                        List<Requirement> blameReqs = new ArrayList<Requirement>();
                        blameReqs.add(blame.m_reqs.get(0));

                        mergeUses(
                            session,
                            resource,
                            resourcePkgs,
                            blame.m_cap,
                            blameReqs,
                            null,
                            resourcePkgMap,
                            allCandidates,
                            usesCycleMap);
                    }
                }
            }
            // Merge uses constraints from required bundles.
            for (Entry<String, List<Blame>> entry : resourcePkgs.m_requiredPkgs.entrySet())
            {
                for (Blame blame : entry.getValue())
                {
                    List<Requirement> blameReqs = new ArrayList<Requirement>();
                    blameReqs.add(blame.m_reqs.get(0));

                    mergeUses(
                        session,
                        resource,
                        resourcePkgs,
                        blame.m_cap,
                        blameReqs,
                        null,
                        resourcePkgMap,
                        allCandidates,
                        usesCycleMap);
                }
            }
        }
    }

    private void mergeCandidatePackages(
        ResolveContext rc, Resource current, Requirement currentReq,
        Capability candCap, Map<Resource, Packages> resourcePkgMap,
        Candidates allCandidates, Map<Resource, List<Capability>> cycles)
    {
        List<Capability> cycleCaps = cycles.get(current);
        if (cycleCaps == null)
        {
            cycleCaps = new ArrayList<Capability>();
            cycles.put(current, cycleCaps);
        }
        if (cycleCaps.contains(candCap))
        {
            return;
        }
        cycleCaps.add(candCap);

        if (candCap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
        {
            mergeCandidatePackage(
                current, false, currentReq, candCap, resourcePkgMap);
        }
        else if (candCap.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE))
        {
// TODO: FELIX3 - THIS NEXT LINE IS A HACK. IMPROVE HOW/WHEN WE CALCULATE EXPORTS.
            calculateExportedPackages(
                rc, candCap.getResource(), allCandidates, resourcePkgMap);

            // Get the candidate's package space to determine which packages
            // will be visible to the current resource.
            Packages candPkgs = resourcePkgMap.get(candCap.getResource());

            // We have to merge all exported packages from the candidate,
            // since the current resource requires it.
            for (Entry<String, Blame> entry : candPkgs.m_exportedPkgs.entrySet())
            {
                mergeCandidatePackage(
                    current,
                    true,
                    currentReq,
                    entry.getValue().m_cap,
                    resourcePkgMap);
            }

            // If the candidate requires any other bundles with reexport visibility,
            // then we also need to merge their packages too.
            Wiring candWiring = rc.getWirings().get(candCap.getResource());
            if (candWiring != null)
            {
                for (Wire w : candWiring.getRequiredResourceWires(null))
                {
                    if (w.getRequirement().getNamespace()
                        .equals(BundleNamespace.BUNDLE_NAMESPACE))
                    {
                        String value = w.getRequirement()
                            .getDirectives()
                            .get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE);
                        if ((value != null)
                            && value.equals(BundleNamespace.VISIBILITY_REEXPORT))
                        {
                            mergeCandidatePackages(
                                rc,
                                current,
                                currentReq,
                                w.getCapability(),
                                resourcePkgMap,
                                allCandidates,
                                cycles);
                        }
                    }
                }
            }
            else
            {
                for (Requirement req : candCap.getResource().getRequirements(null))
                {
                    if (req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE))
                    {
                        String value =
                            req.getDirectives()
                            .get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE);
                        if ((value != null)
                            && value.equals(BundleNamespace.VISIBILITY_REEXPORT)
                            && (allCandidates.getCandidates(req) != null))
                        {
                            mergeCandidatePackages(
                                rc,
                                current,
                                currentReq,
                                allCandidates.getCandidates(req).iterator().next(),
                                resourcePkgMap,
                                allCandidates,
                                cycles);
                        }
                    }
                }
            }
        }

        cycles.remove(current);
    }

    private void mergeCandidatePackage(
        Resource current, boolean requires,
        Requirement currentReq, Capability candCap,
        Map<Resource, Packages> resourcePkgMap)
    {
        if (candCap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
        {
            // Merge the candidate capability into the resource's package space
            // for imported or required packages, appropriately.

            String pkgName = (String) candCap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);

            List<Requirement> blameReqs = new ArrayList<Requirement>();
            blameReqs.add(currentReq);

            Packages currentPkgs = resourcePkgMap.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));

//dumpResourcePkgs(current, currentPkgs);
        }
    }

    private void mergeUses(
        ResolveSession session, Resource current, Packages currentPkgs,
        Capability mergeCap, List<Requirement> blameReqs, Capability matchingCap,
        Map<Resource, Packages> resourcePkgMap,
        Candidates allCandidates,
        Map<Capability, List<Resource>> cycleMap)
    {
        // If there are no uses, then just return.
        // If the candidate resource is the same as the current resource,
        // 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.getResource()))
        {
            return;
        }

        // Check for cycles.
        List<Resource> list = cycleMap.get(mergeCap);
        if ((list != null) && list.contains(current))
        {
            return;
        }
        list = (list == null) ? new ArrayList<Resource>() : list;
        list.add(current);
        cycleMap.put(mergeCap, list);

        for (Capability candSourceCap : getPackageSources(session, mergeCap, resourcePkgMap))
        {
            List<String> uses;
// TODO: RFC-112 - Need impl-specific type
//            if (candSourceCap instanceof FelixCapability)
//            {
//                uses = ((FelixCapability) candSourceCap).getUses();
//            }
//            else
            {
                uses = Collections.EMPTY_LIST;
                String s = candSourceCap.getDirectives()
                    .get(Namespace.CAPABILITY_USES_DIRECTIVE);
                if (s != null)
                {
                    // Parse these uses directive.
                    StringTokenizer tok = new StringTokenizer(s, ",");
                    uses = new ArrayList(tok.countTokens());
                    while (tok.hasMoreTokens())
                    {
                        uses.add(tok.nextToken().trim());
                    }
                }
            }
            for (String usedPkgName : uses)
            {
                Packages candSourcePkgs = resourcePkgMap.get(candSourceCap.getResource());
                List<Blame> candSourceBlames;
                // 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<UsedBlames> usedPkgBlames = currentPkgs.m_usedPkgs.get(usedPkgName);
                if (usedPkgBlames == null)
                {
                    usedPkgBlames = new ArrayList<UsedBlames>();
                    currentPkgs.m_usedPkgs.put(usedPkgName, usedPkgBlames);
                }
                for (Blame blame : candSourceBlames)
                {
                    if (blame.m_reqs != null)
                    {
                        List<Requirement> blameReqs2 = new ArrayList<Requirement>(blameReqs);
                        // Only add the last requirement in blame chain because
                        // that is the requirement wired to the blamed capability
                        blameReqs2.add(blame.m_reqs.get(blame.m_reqs.size() - 1));
                        addUsedBlame(usedPkgBlames, blame.m_cap, blameReqs2, matchingCap);
                        mergeUses(session, current, currentPkgs, blame.m_cap, blameReqs2, matchingCap,
                            resourcePkgMap, allCandidates, cycleMap);
                    }
                    else
                    {
                        addUsedBlame(usedPkgBlames, blame.m_cap, blameReqs, matchingCap);
                        mergeUses(session, current, currentPkgs, blame.m_cap, blameReqs, matchingCap,
                            resourcePkgMap, allCandidates, cycleMap);
                    }
                }
            }
        }
    }

    private static void addUsedBlame(
        List<UsedBlames> usedBlames, Capability usedCap,
        List<Requirement> blameReqs, Capability matchingCap)
    {
        // Create a new Blame based off the used capability and the
        // blame chain requirements.
        Blame newBlame = new Blame(usedCap, blameReqs);
        // Find UsedBlame that uses the same capablity as the new blame.
        UsedBlames addToBlame = null;
        for (UsedBlames usedBlame : usedBlames)
        {
            if (usedCap.equals(usedBlame.m_cap))
            {
                addToBlame = usedBlame;
                break;
            }
        }
        if (addToBlame == null)
        {
            // If none exist create a new UsedBlame for the capability.
            addToBlame = new UsedBlames(usedCap);
            usedBlames.add(addToBlame);
        }
        // Add the new Blame and record the matching capability cause
        // in case the root requirement has multiple cardinality.
        addToBlame.addBlame(newBlame, matchingCap);
    }

    private void checkPackageSpaceConsistency(
        ResolveSession session,
        Resource resource,
        Candidates allCandidates,
        Map<Resource, Packages> resourcePkgMap,
        Map<Resource, Object> resultCache) throws ResolutionException
    {
        if (session.getContext().getWirings().containsKey(resource))
        {
            return;
        }
        checkDynamicPackageSpaceConsistency(
            session, resource, allCandidates, resourcePkgMap, resultCache);
    }

    private void checkDynamicPackageSpaceConsistency(
        ResolveSession session,
        Resource resource,
        Candidates allCandidates,
        Map<Resource, Packages> resourcePkgMap,
        Map<Resource, Object> resultCache) throws ResolutionException
    {
        if (resultCache.containsKey(resource))
        {
            return;
        }

        Packages pkgs = resourcePkgMap.get(resource);

        ResolutionException rethrow = null;
        Candidates permutation = null;
        Set<Requirement> mutated = null;

        List<Candidates> importPermutations = session.getImportPermutations();
        List<Candidates> usesPermutations = session.getUsesPermutations();

        // Check for conflicting imports from fragments.
        // TODO: Is this only needed for imports or are generic and bundle requirements also needed?
        //       I think this is only a special case for fragment imports because they can overlap
        //       host imports, which is not allowed in normal metadata.
        for (Entry<String, List<Blame>> entry : pkgs.m_importedPkgs.entrySet())
        {
            if (entry.getValue().size() > 1)
            {
                Blame sourceBlame = null;
                for (Blame blame : entry.getValue())
                {
                    if (sourceBlame == null)
                    {
                        sourceBlame = blame;
                    }
                    else if (!sourceBlame.m_cap.getResource().equals(blame.m_cap.getResource()))
                    {
                        // Try to permutate the conflicting requirement.
                        permutate(allCandidates, blame.m_reqs.get(0), importPermutations);
                        // Try to permutate the source requirement.
                        permutate(allCandidates, sourceBlame.m_reqs.get(0), importPermutations);
                        // Report conflict.
                        ResolutionException ex = new ResolutionException(
                            "Uses constraint violation. Unable to resolve resource "
                            + Util.getSymbolicName(resource)
                            + " [" + resource
                            + "] because it is exposed to package '"
                            + entry.getKey()
                            + "' from resources "
                            + Util.getSymbolicName(sourceBlame.m_cap.getResource())
                            + " [" + sourceBlame.m_cap.getResource()
                            + "] and "
                            + Util.getSymbolicName(blame.m_cap.getResource())
                            + " [" + blame.m_cap.getResource()
                            + "] via two dependency chains.\n\nChain 1:\n"
                            + toStringBlame(session.getContext(), allCandidates, sourceBlame)
                            + "\n\nChain 2:\n"
                            + toStringBlame(session.getContext(), allCandidates, blame),
                            null,
                            Collections.singleton(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;
                    }
                }
            }
        }

        // Check if there are any uses conflicts with exported packages.
        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 (UsedBlames usedBlames : pkgs.m_usedPkgs.get(pkgName))
            {
                if (!isCompatible(session, Collections.singletonList(exportBlame), usedBlames.m_cap, resourcePkgMap))
                {
                    for (Blame usedBlame : usedBlames.m_blames)
                    {
                        if (checkMultiple(session, usedBlames, usedBlame, allCandidates))
                        {
                            // Continue to the next usedBlame, if possible we
                            // removed the conflicting candidates.
                            continue;
                        }
                        // 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 ResolutionException(
                                "Uses constraint violation. Unable to resolve resource "
                                + Util.getSymbolicName(resource)
                                + " [" + resource
                                + "] because it exports package '"
                                + pkgName
                                + "' and is also exposed to it from resource "
                                + Util.getSymbolicName(usedBlame.m_cap.getResource())
                                + " [" + usedBlame.m_cap.getResource()
                                + "] via the following dependency chain:\n\n"
                                + toStringBlame(session.getContext(), allCandidates, usedBlame),
                                null,
                                null);

                        mutated = (mutated != null)
                            ? mutated
                            : new HashSet<Requirement>();

                        for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--)
                        {
                            Requirement req = usedBlame.m_reqs.get(reqIdx);
                            // Sanity check for multiple.
                            if (Util.isMultiple(req))
                            {
                                continue;
                            }
                            // 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 resource
                            // associated with the requirement is already resolved.
                            List<Capability> candidates = permutation.getCandidates(req);
                            if ((candidates != null) && (candidates.size() > 1 || Util.isOptional(req)))
                            {
                                mutated.add(req);
                                // Remove the conflicting candidate.
                                candidates.remove(0);
                                if (candidates.isEmpty())
                                {
                                    permutation.clearCandidates(req);
                                }
                                // Continue with the next uses constraint.
                                break;
                            }
                        }
                    }
                }
            }

            if (rethrow != null)
            {
                if (!mutated.isEmpty())
                {
                    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 and required packages.
        // We combine the imported and required packages here into one map.
        // Imported packages are added after required packages because they shadow or override
        // the packages from required bundles.
        Map<String, List<Blame>> allImportRequirePkgs =
            new HashMap<String, List<Blame>>(pkgs.m_requiredPkgs);
        allImportRequirePkgs.putAll(pkgs.m_importedPkgs);

        for (Entry<String, List<Blame>> requirementBlames : allImportRequirePkgs.entrySet())
        {
            String pkgName = requirementBlames.getKey();
            if (!pkgs.m_usedPkgs.containsKey(pkgName))
            {
                continue;
            }

            for (UsedBlames usedBlames : pkgs.m_usedPkgs.get(pkgName))
            {
                if (!isCompatible(session, requirementBlames.getValue(), usedBlames.m_cap, resourcePkgMap))
                {
                    // Split packages, need to think how to get a good message for split packages (sigh)
                    // For now we just use the first requirement that brings in the package that conflicts
                    Blame requirementBlame = requirementBlames.getValue().get(0);
                    for (Blame usedBlame : usedBlames.m_blames)
                    {
                        if (checkMultiple(session, usedBlames, usedBlame, allCandidates))
                        {
                            // Continue to the next usedBlame, if possible we
                            // removed the conflicting candidates.
                            continue;
                        }
                        // 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 ResolutionException(
                                "Uses constraint violation. Unable to resolve resource "
                                + Util.getSymbolicName(resource)
                                + " [" + resource
                                + "] because it is exposed to package '"
                                + pkgName
                                + "' from resources "
                                + Util.getSymbolicName(requirementBlame.m_cap.getResource())
                                + " [" + requirementBlame.m_cap.getResource()
                                + "] and "
                                + Util.getSymbolicName(usedBlame.m_cap.getResource())
                                + " [" + usedBlame.m_cap.getResource()
                                + "] via two dependency chains.\n\nChain 1:\n"
                                + toStringBlame(session.getContext(), allCandidates, requirementBlame)
                                + "\n\nChain 2:\n"
                                + toStringBlame(session.getContext(), allCandidates, usedBlame),
                                null,
                                null);

                        mutated = (mutated != null)
                            ? mutated
                            : new HashSet<Requirement>();

                        for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--)
                        {
                            Requirement req = usedBlame.m_reqs.get(reqIdx);
                            // Sanity check for multiple.
                            if (Util.isMultiple(req))
                            {
                                continue;
                            }
                            // 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 resource
                            // associated with the requirement is already resolved.
                            List<Capability> candidates = permutation.getCandidates(req);
                            if ((candidates != null) && (candidates.size() > 1 || Util.isOptional(req)))
                            {
                                mutated.add(req);
                                // Remove the conflicting candidate.
                                candidates.remove(0);
                                if (candidates.isEmpty())
                                {
                                    permutation.clearCandidates(req);
                                }
                                // 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.isEmpty())
                    {
                        usesPermutations.add(permutation);
                    }

                    // Try to permutate the candidate for the original
                    // import requirement; only permutate it if we haven't
                    // done so already.
                    for (Blame requirementBlame : requirementBlames.getValue())
                    {
                        Requirement req = requirementBlame.m_reqs.get(0);
                        if (!mutated.contains(req))
                        {
                            // Since there may be lots of uses constraint violations
                            // with existing import decisions, we may end up trying
                            // to permutate the same import a lot of times, so we should
                            // try to check if that the case and only permutate it once.
                            permutateIfNeeded(allCandidates, req, 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(resource, Boolean.TRUE);

        // Now check the consistency of all resources on which the
        // current resource depends. Keep track of the current number
        // of permutations so we know if the lower level check was
        // able to create a permutation or not in the case of failure.
        int permCount = usesPermutations.size() + importPermutations.size();
        for (Requirement req : resource.getRequirements(null))
        {
            List<Capability> cands = allCandidates.getCandidates(req);
            if (cands != null && !cands.isEmpty())
            {
                Capability cap = cands.get(0);
                if (!resource.equals(cap.getResource()))
                {
                    try
                    {
                        checkPackageSpaceConsistency(
                            session, cap.getResource(),
                            allCandidates, resourcePkgMap, resultCache);
                    }
                    catch (ResolutionException 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 resource
                        // to backtrack on our current candidate selection.
                        if (permCount == (usesPermutations.size() + importPermutations.size()))
                        {
                            permutate(allCandidates, req, importPermutations);
                        }
                        throw ex;
                    }
                }
            }
        }
    }

    private boolean checkMultiple(
        ResolveSession session,
        UsedBlames usedBlames,
        Blame usedBlame,
        Candidates permutation)
    {
        // Check the root requirement to see if it is a multiple cardinality
        // requirement.
        List<Capability> candidates = null;
        Requirement req = usedBlame.m_reqs.get(0);
        if (Util.isMultiple(req))
        {
            // Create a copy of the current permutation so we can remove the
            // candidates causing the blame.
            if (session.getMultipleCardCandidates() == null)
            {
                session.setMultipleCardCandidates(permutation.copy());
            }
            // Get the current candidate list and remove all the offending root
            // cause candidates from a copy of the current permutation.
            candidates = session.getMultipleCardCandidates().getCandidates(req);
            candidates.removeAll(usedBlames.getRootCauses(req));
        }
        // We only are successful if there is at least one candidate left
        // for the requirement
        return (candidates != null) && !candidates.isEmpty();
    }

    private static void permutate(
        Candidates allCandidates, Requirement req, List<Candidates> permutations)
    {
        if (!Util.isMultiple(req))
        {
            List<Capability> candidates = allCandidates.getCandidates(req);
            if ((candidates != null) && (candidates.size() > 1 || Util.isOptional(req)))
            {
                Candidates perm = allCandidates.copy();
                candidates = perm.getCandidates(req);
                candidates.remove(0);
                if (candidates.isEmpty())
                {
                    perm.clearCandidates(req);
                }
                permutations.add(perm);
            }
        }
    }

    static void permutateIfNeeded(
        Candidates allCandidates, Requirement req, List<Candidates> permutations)
    {
        List<Capability> candidates = allCandidates.getCandidates(req);
        if ((candidates != null) && (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)
            {
                List<Capability> existingPermCands = existingPerm.getCandidates(req);
                if (existingPermCands != null && !existingPermCands.get(0).equals(candidates.get(0)))
                {
                    permutated = true;
                }
            }
            // If we haven't already permutated the existing
            // import, do so now.
            if (!permutated)
            {
                permutate(allCandidates, req, permutations);
            }
        }
    }

    private static void calculateExportedPackages(
        ResolveContext rc,
        Resource resource,
        Candidates allCandidates,
        Map<Resource, Packages> resourcePkgMap)
    {
        Packages packages = resourcePkgMap.get(resource);
        if (packages != null)
        {
            return;
        }
        packages = new Packages(resource);

        // Get all exported packages.
        Wiring wiring = rc.getWirings().get(resource);
        List<Capability> caps = (wiring != null)
            ? wiring.getResourceCapabilities(null)
            : resource.getCapabilities(null);
        Map<String, Capability> exports = new HashMap<String, Capability>(caps.size());
        for (Capability cap : caps)
        {
            if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
            {
                if (!cap.getResource().equals(resource))
                {
                    cap = new WrappedCapability(resource, cap);
                }
                exports.put(
                    (String) cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE),
                    cap);
            }
        }
        // Remove substitutable exports that were imported.
        // For resolved resources Wiring.getCapabilities()
        // already excludes imported substitutable exports, but
        // for resolving resources we must look in the candidate
        // map to determine which exports are substitutable.
        if (!exports.isEmpty())
        {
            if (wiring == null)
            {
                for (Requirement req : resource.getRequirements(null))
                {
                    if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
                    {
                        List<Capability> cands = allCandidates.getCandidates(req);
                        if ((cands != null) && !cands.isEmpty())
                        {
                            String pkgName = (String) cands.get(0)
                                .getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
                            exports.remove(pkgName);
                        }
                    }
                }
            }

            // Add all non-substituted exports to the resources's package space.
            for (Entry<String, Capability> entry : exports.entrySet())
            {
                packages.m_exportedPkgs.put(
                    entry.getKey(), new Blame(entry.getValue(), null));
            }
        }

        resourcePkgMap.put(resource, packages);
    }

    private boolean isCompatible(
        ResolveSession session, List<Blame> currentBlames, Capability candCap,
        Map<Resource, Packages> resourcePkgMap)
    {
        if ((!currentBlames.isEmpty()) && (candCap != null))
        {
            List<Capability> currentSources;
            // quick check for single source package
            if (currentBlames.size() == 1)
            {
                Capability currentCap = currentBlames.get(0).m_cap;
                if (currentCap.equals(candCap))
                {
                    return true;
                }
                currentSources =
                    getPackageSources(
                        session,
                        currentCap,
                        resourcePkgMap);
            }
            else
            {
                currentSources = new ArrayList<Capability>(currentBlames.size());
                for (Blame currentBlame : currentBlames)
                {
                    List<Capability> blameSources =
                        getPackageSources(
                            session,
                            currentBlame.m_cap,
                            resourcePkgMap);
                    for (Capability blameSource : blameSources)
                    {
                        if (!currentSources.contains(blameSource))
                        {
                            currentSources.add(blameSource);
                        }
                    }
                }
            }

            List<Capability> candSources =
                getPackageSources(
                    session,
                    candCap,
                    resourcePkgMap);

            return currentSources.containsAll(candSources)
                || candSources.containsAll(currentSources);
        }
        return true;
    }

    private List<Capability> getPackageSources(
        ResolveSession session, Capability cap, Map<Resource, Packages> resourcePkgMap)
    {
        Map<Capability, List<Capability>> packageSourcesCache = session.getPackageSourcesCache();
        // If it is a package, then calculate sources for it.
        if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
        {
            List<Capability> sources = packageSourcesCache.get(cap);
            if (sources == null)
            {
                sources = getPackageSourcesInternal(
                    session.getContext(), cap, resourcePkgMap, new ArrayList(), new HashSet());
                packageSourcesCache.put(cap, sources);
            }
            return sources;
        }

        // Otherwise, need to return generic capabilies that have
        // uses constraints so they are included for consistency
        // checking.
        String uses = cap.getDirectives().get(Namespace.CAPABILITY_USES_DIRECTIVE);
        if ((uses != null) && (uses.length() > 0))
        {
            return Collections.singletonList(cap);
        }

        return Collections.EMPTY_LIST;
    }

    private static List<Capability> getPackageSourcesInternal(
        ResolveContext rc, Capability cap, Map<Resource, Packages> resourcePkgMap,
        List<Capability> sources, Set<Capability> cycleMap)
    {
        if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
        {
            if (cycleMap.contains(cap))
            {
                return sources;
            }
            cycleMap.add(cap);

            // Get the package name associated with the capability.
            String pkgName = cap.getAttributes()
                .get(PackageNamespace.PACKAGE_NAMESPACE).toString();

            // Since a resource can export the same package more than once, get
            // all package capabilities for the specified package name.
            Wiring wiring = rc.getWirings().get(cap.getResource());
            List<Capability> caps = (wiring != null)
                ? wiring.getResourceCapabilities(null)
                : cap.getResource().getCapabilities(null);
            for (Capability sourceCap : caps)
            {
                if (sourceCap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)
                    && sourceCap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE).equals(pkgName))
                {
                    // Since capabilities may come from fragments, we need to check
                    // for that case and wrap them.
                    if (!cap.getResource().equals(sourceCap.getResource()))
                    {
                        sourceCap = new WrappedCapability(cap.getResource(), sourceCap);
                    }
                    if (!sources.contains(sourceCap))
                    {
                        sources.add(sourceCap);
                    }
                }
            }

            // Then get any addition sources for the package from required bundles.
            Packages pkgs = resourcePkgMap.get(cap.getResource());
            List<Blame> required = pkgs.m_requiredPkgs.get(pkgName);
            if (required != null)
            {
                for (Blame blame : required)
                {
                    getPackageSourcesInternal(rc, blame.m_cap, resourcePkgMap, sources, cycleMap);
                }
            }
        }

        return sources;
    }

    private static Resource getDeclaredResource(Resource resource)
    {
        if (resource instanceof WrappedResource)
        {
            return ((WrappedResource) resource).getDeclaredResource();
        }
        return resource;
    }

    private static Capability getDeclaredCapability(Capability c)
    {
        if (c instanceof HostedCapability)
        {
            return ((HostedCapability) c).getDeclaredCapability();
        }
        return c;
    }

    private static Requirement getDeclaredRequirement(Requirement r)
    {
        if (r instanceof WrappedRequirement)
        {
            return ((WrappedRequirement) r).getDeclaredRequirement();
        }
        return r;
    }

    private static Map<Resource, List<Wire>> populateWireMap(
        ResolveContext rc, Resource resource, Map<Resource, Packages> resourcePkgMap,
        Map<Resource, List<Wire>> wireMap, Candidates allCandidates)
    {
        Resource unwrappedResource = getDeclaredResource(resource);
        if (!rc.getWirings().containsKey(unwrappedResource)
            && !wireMap.containsKey(unwrappedResource))
        {
            wireMap.put(unwrappedResource, (List<Wire>) Collections.EMPTY_LIST);

            List<Wire> packageWires = new ArrayList<Wire>();
            List<Wire> bundleWires = new ArrayList<Wire>();
            List<Wire> capabilityWires = new ArrayList<Wire>();

            for (Requirement req : resource.getRequirements(null))
            {
                List<Capability> cands = allCandidates.getCandidates(req);
                if ((cands != null) && (cands.size() > 0))
                {
                    for (Capability cand : cands)
                    {
                        // Do not create wires for the osgi.wiring.* namespaces
                        // if the provider and requirer are the same resource;
                        // allow such wires for non-OSGi wiring namespaces.
                        if (!cand.getNamespace().startsWith("osgi.wiring.")
                            || !resource.equals(cand.getResource()))
                        {
                            // If we don't already have wires for the candidate,
                            // then recursively populate them.
                            if (!rc.getWirings().containsKey(cand.getResource()))
                            {
                                // Need to special case the candidate for identity
                                // capabilities since it may be from a fragment and
                                // we don't want to populate wires for the fragment,
                                // but rather the host to which it is attached.
                                Resource targetCand = cand.getResource();
                                if (IdentityNamespace.IDENTITY_NAMESPACE.equals(cand.getNamespace())
                                    && Util.isFragment(targetCand))
                                {
                                    targetCand = allCandidates.getCandidates(
                                        targetCand.getRequirements(HostNamespace.HOST_NAMESPACE).get(0))
                                        .iterator().next().getResource();
                                    targetCand = allCandidates.getWrappedHost(targetCand);
                                }

                                populateWireMap(rc, targetCand,
                                    resourcePkgMap, wireMap, allCandidates);
                            }

                            Wire wire = new WireImpl(
                                unwrappedResource,
                                getDeclaredRequirement(req),
                                getDeclaredResource(cand.getResource()),
                                getDeclaredCapability(cand));
                            if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
                            {
                                packageWires.add(wire);
                            }
                            else if (req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE))
                            {
                                bundleWires.add(wire);
                            }
                            else
                            {
                                capabilityWires.add(wire);
                            }
                        }
                        if (!Util.isMultiple(req))
                        {
                            // If not multiple just create a wire for the first candidate.
                            break;
                        }
                    }
                }
            }

            // Combine package wires with require wires last.
            packageWires.addAll(bundleWires);
            packageWires.addAll(capabilityWires);
            wireMap.put(unwrappedResource, packageWires);

            // Add host wire for any fragments.
            if (resource instanceof WrappedResource)
            {
                List<Resource> fragments = ((WrappedResource) resource).getFragments();
                for (Resource fragment : fragments)
                {
                    // Get wire list for the fragment from the wire map.
                    // If there isn't one, then create one. Note that we won't
                    // add the wire list to the wire map until the end, so
                    // we can determine below if this is the first time we've
                    // seen the fragment while populating wires to avoid
                    // creating duplicate non-payload wires if the fragment
                    // is attached to more than one host.
                    List<Wire> fragmentWires = wireMap.get(fragment);
                    fragmentWires = (fragmentWires == null)
                        ? new ArrayList<Wire>() : fragmentWires;

                    // Loop through all of the fragment's requirements and create
                    // any necessary wires for non-payload requirements.
                    for (Requirement req : fragment.getRequirements(null))
                    {
                        // Only look at non-payload requirements.
                        if (!isPayload(req))
                        {
                            // If this is the host requirement, then always create
                            // a wire for it to the current resource.
                            if (req.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
                            {
                                fragmentWires.add(
                                    new WireImpl(
                                        getDeclaredResource(fragment),
                                        req,
                                        unwrappedResource,
                                        unwrappedResource.getCapabilities(
                                            HostNamespace.HOST_NAMESPACE).get(0)));
                            }
                            // Otherwise, if the fragment isn't already resolved and
                            // this is the first time we are seeing it, then create
                            // a wire for the non-payload requirement.
                            else if (!rc.getWirings().containsKey(fragment)
                                && !wireMap.containsKey(fragment))
                            {
                                Wire wire = createWire(req, allCandidates);
                                if (wire != null)
                                {
                                    fragmentWires.add(wire);
                                }
                            }
                        }
                    }

                    // Finally, add the fragment's wire list to the wire map.
                    wireMap.put(fragment, fragmentWires);
                }
            }
        }

        return wireMap;
    }

    private static Wire createWire(Requirement requirement, Candidates allCandidates)
    {
        List<Capability> candidates = allCandidates.getCandidates(requirement);
        if (candidates == null || candidates.isEmpty())
        {
            return null;
        }
        Capability cand = candidates.get(0);
        return new WireImpl(
            getDeclaredResource(requirement.getResource()),
            getDeclaredRequirement(requirement),
            getDeclaredResource(cand.getResource()),
            getDeclaredCapability(cand));
    }

    private static boolean isPayload(Requirement fragmentReq)
    {
        // this is where we would add other non-payload namespaces
        if (ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE
            .equals(fragmentReq.getNamespace()))
        {
            return false;
        }
        if (HostNamespace.HOST_NAMESPACE.equals(fragmentReq.getNamespace()))
        {
            return false;
        }
        return true;
    }

    private static Map<Resource, List<Wire>> populateDynamicWireMap(
        ResolveContext rc, Resource resource, Requirement dynReq,
        Map<Resource, Packages> resourcePkgMap,
        Map<Resource, List<Wire>> wireMap, Candidates allCandidates)
    {
        wireMap.put(resource, (List<Wire>) Collections.EMPTY_LIST);

        List<Wire> packageWires = new ArrayList<Wire>();

        // Get the candidates for the current dynamic requirement.
        List<Capability> candCaps = allCandidates.getCandidates(dynReq);
        // Record the dynamic candidate.
        Capability dynCand = candCaps.get(0);

        if (!rc.getWirings().containsKey(dynCand.getResource()))
        {
            populateWireMap(rc, dynCand.getResource(), resourcePkgMap,
                wireMap, allCandidates);
        }

        packageWires.add(
            new WireImpl(
                resource,
                dynReq,
                getDeclaredResource(dynCand.getResource()),
                getDeclaredCapability(dynCand)));

        wireMap.put(resource, packageWires);

        return wireMap;
    }

    private static void dumpResourcePkgMap(
        ResolveContext rc, Map<Resource, Packages> resourcePkgMap)
    {
        System.out.println("+++RESOURCE PKG MAP+++");
        for (Entry<Resource, Packages> entry : resourcePkgMap.entrySet())
        {
            dumpResourcePkgs(rc, entry.getKey(), entry.getValue());
        }
    }

    private static void dumpResourcePkgs(
        ResolveContext rc, Resource resource, Packages packages)
    {
        Wiring wiring = rc.getWirings().get(resource);
        System.out.println(resource
            + " (" + ((wiring != 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<UsedBlames>> entry : packages.m_usedPkgs.entrySet())
        {
            System.out.println("    " + entry.getKey() + " - " + entry.getValue());
        }
    }

    private static String toStringBlame(
        ResolveContext rc, Candidates allCandidates, 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++)
            {
                Requirement req = blame.m_reqs.get(i);
                sb.append("  ");
                sb.append(Util.getSymbolicName(req.getResource()));
                sb.append(" [");
                sb.append(req.getResource().toString());
                sb.append("]\n");
                if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
                {
                    sb.append("    import: ");
                }
                else
                {
                    sb.append("    require: ");
                }
                sb.append(req.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE));
                sb.append("\n     |");
                if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
                {
                    sb.append("\n    export: ");
                }
                else
                {
                    sb.append("\n    provide: ");
                }
                if ((i + 1) < blame.m_reqs.size())
                {
                    Capability cap = getSatisfyingCapability(
                        rc,
                        allCandidates,
                        blame.m_reqs.get(i));
                    if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
                    {
                        sb.append(PackageNamespace.PACKAGE_NAMESPACE);
                        sb.append("=");
                        sb.append(cap.getAttributes()
                            .get(PackageNamespace.PACKAGE_NAMESPACE).toString());
                        Capability usedCap =
                            getSatisfyingCapability(
                                rc,
                                allCandidates,
                                blame.m_reqs.get(i + 1));
                        sb.append("; uses:=");
                        sb.append(usedCap.getAttributes()
                            .get(PackageNamespace.PACKAGE_NAMESPACE));
                    }
                    else
                    {
                        sb.append(cap);
                    }
                    sb.append("\n");
                }
                else
                {
                    Capability export = getSatisfyingCapability(
                        rc,
                        allCandidates,
                        blame.m_reqs.get(i));
                    sb.append(export.getNamespace());
                    sb.append(": ");
                    Object namespaceVal = export.getAttributes().get(export.getNamespace());
                    if (namespaceVal != null)
                    {
                        sb.append(namespaceVal.toString());
                    }
                    else
                    {
                        for (Entry<String, Object> attrEntry : export.getAttributes().entrySet())
                        {
                            sb.append(attrEntry.getKey()).append('=')
                                .append(attrEntry.getValue()).append(';');
                        }
                    }
                    if (export.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)
                        && !export.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE)
                        .equals(blame.m_cap.getAttributes().get(
                                PackageNamespace.PACKAGE_NAMESPACE)))
                    {
                        sb.append("; uses:=");
                        sb.append(blame.m_cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE));
                        sb.append("\n    export: ");
                        sb.append(PackageNamespace.PACKAGE_NAMESPACE);
                        sb.append("=");
                        sb.append(blame.m_cap.getAttributes()
                            .get(PackageNamespace.PACKAGE_NAMESPACE).toString());
                    }
                    sb.append("\n  ");
                    sb.append(Util.getSymbolicName(blame.m_cap.getResource()));
                    sb.append(" [");
                    sb.append(blame.m_cap.getResource().toString());
                    sb.append("]");
                }
            }
        }
        else
        {
            sb.append(blame.m_cap.getResource().toString());
        }
        return sb.toString();
    }

    private static Capability getSatisfyingCapability(
        ResolveContext rc, Candidates allCandidates, Requirement req)
    {
        Capability cap = null;

        // If the requiring revision is not resolved, then check in the
        // candidate map for its matching candidate.
        List<Capability> cands = allCandidates.getCandidates(req);
        if (cands != null)
        {
            cap = cands.get(0);
        }
        // Otherwise, if the requiring revision is resolved then check
        // in its wires for the capability satisfying the requirement.
        else if (rc.getWirings().containsKey(req.getResource()))
        {
            List<Wire> wires =
                rc.getWirings().get(req.getResource()).getRequiredResourceWires(null);
            req = getDeclaredRequirement(req);
            for (Wire w : wires)
            {
                if (w.getRequirement().equals(req))
                {
// TODO: RESOLVER - This is not 100% correct, since requirements for
//       dynamic imports with wildcards will reside on many wires and
//       this code only finds the first one, not necessarily the correct
//       one. This is only used for the diagnostic message, but it still
//       could confuse the user.
                    cap = w.getCapability();
                    break;
                }
            }
        }

        return cap;
    }

    private static class Packages
    {
        private final Resource m_resource;
        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<UsedBlames>> m_usedPkgs = new HashMap();
        public boolean m_isCalculated = false;

        public Packages(Resource resource)
        {
            m_resource = resource;
        }
    }

    private static class Blame
    {
        public final Capability m_cap;
        public final List<Requirement> m_reqs;

        public Blame(Capability cap, List<Requirement> reqs)
        {
            m_cap = cap;
            m_reqs = reqs;
        }

        @Override
        public String toString()
        {
            return m_cap.getResource()
                + "." + m_cap.getAttributes().get(PackageNamespace.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);
        }
    }

    /*
     * UsedBlames hold a list of Blame that have a common used capability.
     * The UsedBlames stores sets of capabilities (root causes) that match a
     * root requirement with multiple cardinality.  These causes are the
     * capabilities that pulled in the common used capability.
     * It is assumed that multiple cardinality requirements can only be
     * root requirements of a Blame.
     *
     * This is only true because capabilities can only use a package
     * capability.  They cannot use any other kind of capability so we
     * do not have to worry about transitivity of the uses directive
     * from other capability types.
     */
    private static class UsedBlames
    {
        public final Capability m_cap;
        public final List<Blame> m_blames = new ArrayList<ResolverImpl.Blame>();
        private Map<Requirement, Set<Capability>> m_rootCauses;

        public UsedBlames(Capability cap)
        {
            m_cap = cap;
        }

        public void addBlame(Blame blame, Capability matchingRootCause)
        {
            if (!m_cap.equals(blame.m_cap))
            {
                throw new IllegalArgumentException(
                    "Attempt to add a blame with a different used capability: "
                    + blame.m_cap);
            }
            m_blames.add(blame);
            if (matchingRootCause != null)
            {
                Requirement req = blame.m_reqs.get(0);
                // Assumption made that the root requirement of the chain is the only
                // possible multiple cardinality requirement and that the matching root cause
                // capability is passed down from the beginning of the chain creation.
                if (Util.isMultiple(req))
                {
                    // The root requirement is multiple. Need to store the root cause
                    // so that we can find it later in case the used capability which the cause
                    // capability pulled in is a conflict.
                    if (m_rootCauses == null)
                    {
                        m_rootCauses = new HashMap<Requirement, Set<Capability>>();
                    }
                    Set<Capability> rootCauses = m_rootCauses.get(req);
                    if (rootCauses == null)
                    {
                        rootCauses = new HashSet<Capability>();
                        m_rootCauses.put(req, rootCauses);
                    }
                    rootCauses.add(matchingRootCause);
                }
            }
        }

        public Set<Capability> getRootCauses(Requirement req)
        {
            if (m_rootCauses == null)
            {
                return Collections.EMPTY_SET;
            }
            Set<Capability> result = m_rootCauses.get(req);
            return result == null ? Collections.EMPTY_SET : result;
        }

        @Override
        public String toString()
        {
            return m_blames.toString();
        }
    }
}
TOP

Related Classes of org.apache.felix.resolver.ResolverImpl$UsedBlames

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.