Return-Path: X-Original-To: apmail-aries-commits-archive@www.apache.org Delivered-To: apmail-aries-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 6A3CD9532 for ; Sun, 22 Apr 2012 19:49:51 +0000 (UTC) Received: (qmail 17796 invoked by uid 500); 22 Apr 2012 19:49:50 -0000 Delivered-To: apmail-aries-commits-archive@aries.apache.org Received: (qmail 17643 invoked by uid 500); 22 Apr 2012 19:49:50 -0000 Mailing-List: contact commits-help@aries.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@aries.apache.org Delivered-To: mailing list commits@aries.apache.org Received: (qmail 17558 invoked by uid 99); 22 Apr 2012 19:49:48 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 22 Apr 2012 19:49:47 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 22 Apr 2012 19:49:43 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id B466D2388B6C; Sun, 22 Apr 2012 19:49:23 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1328953 [3/4] - in /aries/trunk/subsystem/subsystem-core/src/main/java/org/apache: aries/subsystem/core/ aries/subsystem/core/archive/ aries/subsystem/core/internal/ aries/subsystem/core/resource/ aries/subsystem/core/resource/tmp/ felix/r... Date: Sun, 22 Apr 2012 19:49:22 -0000 To: commits@aries.apache.org From: jwross@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120422194923.B466D2388B6C@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/ResolverImpl.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/ResolverImpl.java?rev=1328953&view=auto ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/ResolverImpl.java (added) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/ResolverImpl.java Sun Apr 22 19:49:20 2012 @@ -0,0 +1,1836 @@ +/* + * 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.HostNamespace; +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; + + // Holds candidate permutations based on permutating "uses" chains. + // These permutations are given higher priority. + private final List m_usesPermutations = new ArrayList(); + // Holds candidate permutations based on permutating requirement candidates. + // These permutations represent backtracking on previous decisions. + private final List m_importPermutations = new ArrayList(); + + public ResolverImpl(Logger logger) + { + m_logger = logger; + } + + public Map> resolve(ResolveContext rc) throws ResolutionException + { + Map> wireMap = + new HashMap>(); + Map resourcePkgMap = + new HashMap(); + + Collection mandatoryResources = rc.getMandatoryResources(); + Collection optionalResources = rc.getOptionalResources(); +// TODO: RFC-112 - Need impl-specific type. +// Collection ondemandFragments = (rc instanceof ResolveContextImpl) +// ? ((ResolveContextImpl) rc).getOndemandResources() : Collections.EMPTY_LIST; + Collection ondemandFragments = Collections.EMPTY_LIST; + + boolean retry; + do + { + retry = false; + + try + { + // Create object to hold all candidates. + Candidates allCandidates = new Candidates(); + + // Populate mandatory resources; since these are mandatory + // resources, failure throws a resolve exception. + for (Iterator 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); + } + } + + // Populate ondemand fragments; since these are optional + // resources, failure does not throw a resolve exception. + for (Resource resource : ondemandFragments) + { + boolean isFragment = Util.isFragment(resource); + if (isFragment) + { + allCandidates.populate(rc, resource, Candidates.ON_DEMAND); + } + } + + // 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 allResources = + new HashSet(mandatoryResources); + for (Resource resource : optionalResources) + { + if (allCandidates.isPopulated(resource)) + { + allResources.add(resource); + } + } + + // Record the initial candidate permutation. + m_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> hostReqs = + new HashMap>(); + for (Resource resource : allResources) + { + if (Util.isFragment(resource)) + { + hostReqs.put( + resource, + resource.getRequirements(HostNamespace.HOST_NAMESPACE)); + } + } + + do + { + rethrow = null; + + resourcePkgMap.clear(); + m_packageSourcesCache.clear(); + + allCandidates = (m_usesPermutations.size() > 0) + ? m_usesPermutations.remove(0) + : m_importPermutations.remove(0); +//allCandidates.dump(); + + for (Resource resource : allResources) + { + Resource target = resource; + + // If we are resolving a fragment, then get its + // host candidate and verify it instead. + List hostReq = hostReqs.get(resource); + if (hostReq != null) + { + target = allCandidates.getCandidates(hostReq.get(0)) + .iterator().next().getResource(); + } + + calculatePackageSpaces( + rc, 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( + rc, false, allCandidates.getWrappedHost(target), + allCandidates, resourcePkgMap, new HashMap()); + } + catch (ResolutionException ex) + { + rethrow = ex; + } + } + } + while ((rethrow != null) + && ((m_usesPermutations.size() > 0) || (m_importPermutations.size() > 0))); + + // If there is a resolve exception, then determine if an + // optionally resolved 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) + { + Collection 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(); + } + // Try to ignore the faulty resource if it is not mandatory. + if (optionalResources.remove(faultyResource)) + { + retry = true; + } + else if (ondemandFragments.remove(faultyResource)) + { + retry = true; + } + else + { + throw rethrow; + } + } + // If there is no exception to rethrow, then this was a clean + // resolve, so populate the wire map. + else + { + for (Resource resource : allResources) + { + Resource target = resource; + + // If we are resolving a fragment, then we + // actually want to populate its host's wires. + List 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. + m_usesPermutations.clear(); + m_importPermutations.clear(); + } + } + while (retry); + + return wireMap; + } + +/* + TODO: RFC-112 - Modify dynamic import handling to be like obr-resolver prototype. + public Map> resolve( + ResolveContext rc, Resouce resource, String pkgName) + { + // 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. + // The following call checks all of these conditions and returns + // the associated dynamic import and matching capabilities. + Candidates allCandidates = + getDynamicImportCandidates(rc, resource, pkgName); + if (allCandidates != null) + { + Collection ondemandFragments = (rc instanceof ResolveContextImpl) + ? ((ResolveContextImpl) rc).getOndemandResources() : Collections.EMPTY_LIST; + + Map> wireMap = + new HashMap>(); + Map resourcePkgMap = + new HashMap(); + + boolean retry; + do + { + retry = false; + + try + { + // Try to populate optional fragments. + for (Resource r : ondemandFragments) + { + if (Util.isFragment(r)) + { + allCandidates.populate(rc, r, Candidates.ON_DEMAND); + } + } + + // Merge any fragments into hosts. + allCandidates.prepare(rc); + + // Record the initial candidate permutation. + m_usesPermutations.add(allCandidates); + + ResolveException rethrow = null; + + do + { + rethrow = null; + + resourcePkgMap.clear(); + m_packageSourcesCache.clear(); + + allCandidates = (m_usesPermutations.size() > 0) + ? m_usesPermutations.remove(0) + : m_importPermutations.remove(0); +//allCandidates.dump(); + + // For a dynamic import, the instigating 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( + allCandidates.getWrappedHost(resource), allCandidates, resourcePkgMap, + new HashMap(), new HashSet()); +//System.out.println("+++ PACKAGE SPACES START +++"); +//dumpResourcePkgMap(resourcePkgMap); +//System.out.println("+++ PACKAGE SPACES END +++"); + + try + { + checkPackageSpaceConsistency( + false, allCandidates.getWrappedHost(resource), + allCandidates, resourcePkgMap, new HashMap()); + } + catch (ResolveException ex) + { + rethrow = ex; + } + } + while ((rethrow != null) + && ((m_usesPermutations.size() > 0) || (m_importPermutations.size() > 0))); + + // If there is a resolve exception, then determine if an + // optionally resolved 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) + { + Resource faultyResource = + getDeclaredResource(rethrow.getResource())); + if (rethrow.getRequirement() instanceof WrappedRequirement) + { + faultyResource = + ((WrappedRequirement) rethrow.getRequirement()) + .getOriginalRequirement().getResource()); + } + if (ondemandFragments.remove(faultyResource)) + { + retry = true; + } + else + { + throw rethrow; + } + } + // If there is no exception to rethrow, then this was a clean + // resolve, so populate the wire map. + else + { + wireMap = populateDynamicWireMap( + resource, pkgName, resourcePkgMap, wireMap, allCandidates); + return wireMap; + } + } + finally + { + // Always clear the state. + m_usesPermutations.clear(); + m_importPermutations.clear(); + } + } + while (retry); + } + + return null; + } + + private static Candidates getDynamicImportCandidates( + ResolveContext rc, Resource resource, String pkgName) + { + // Unresolved resources cannot dynamically import, nor can the default + // package be dynamically imported. + if ((resource.getWiring() == null) || pkgName.length() == 0) + { + return null; + } + + // If the resource doesn't have dynamic imports, then just return + // immediately. + List dynamics = + Util.getDynamicRequirements(resource.getWiring().getRequirements(null)); + if ((dynamics == null) || dynamics.isEmpty()) + { + return null; + } + + // If the resource exports this package, then we cannot + // attempt to dynamically import it. + for (Capability cap : resource.getWiring().getCapabilities(null)) + { + if (cap.getNamespace().equals(Resource.PACKAGE_NAMESPACE) + && cap.getAttributes().get(Resource.PACKAGE_NAMESPACE).equals(pkgName)) + { + return null; + } + } + + // If this resource already imports or requires this package, then + // we cannot dynamically import it. + if (((WiringImpl) resource.getWiring()).hasPackageSource(pkgName)) + { + return null; + } + + // Determine if any providers of the package exist. + Map attrs = Collections.singletonMap( + PackageNamespace.PACKAGE_NAMESPACE, (Object) pkgName); + RequirementImpl req = new RequirementImpl( + resource, + PackageNamespace.PACKAGE_NAMESPACE, + Collections.EMPTY_MAP, + attrs); + List candidates = rc.findProviders(req, false); + + // Try to find a dynamic requirement that matches the capabilities. + RequirementImpl dynReq = null; + for (int dynIdx = 0; + (candidates.size() > 0) && (dynReq == null) && (dynIdx < dynamics.size()); + dynIdx++) + { + for (Iterator itCand = candidates.iterator(); + (dynReq == null) && itCand.hasNext(); ) + { + Capability cap = itCand.next(); + if (CapabilitySet.matches( + (CapabilityImpl) cap, + ((RequirementImpl) dynamics.get(dynIdx)).getFilter())) + { + dynReq = (RequirementImpl) dynamics.get(dynIdx); + } + } + } + + // If we found a matching dynamic requirement, then filter out + // any candidates that do not match it. + if (dynReq != null) + { + for (Iterator itCand = candidates.iterator(); + itCand.hasNext(); ) + { + Capability cap = itCand.next(); + if (!CapabilitySet.matches( + (CapabilityImpl) cap, dynReq.getFilter())) + { + itCand.remove(); + } + } + } + else + { + candidates.clear(); + } + + Candidates allCandidates = null; + + if (candidates.size() > 0) + { + allCandidates = new Candidates(); + allCandidates.populateDynamic(rc, resource, dynReq, candidates); + } + + return allCandidates; + } +*/ + private void calculatePackageSpaces( + ResolveContext rc, + Resource resource, + Candidates allCandidates, + Map resourcePkgMap, + Map> usesCycleMap, + Set cycle) + { + if (cycle.contains(resource)) + { + return; + } + cycle.add(resource); + + // Make sure package space hasn't already been calculated. + if (resourcePkgMap.containsKey(resource)) + { + return; + } + + // Create parallel arrays for requirement and proposed candidate + // capability or actual capability if resource is resolved or not. + List reqs = new ArrayList(); + List caps = new ArrayList(); + boolean isDynamicImporting = false; + Wiring wiring = rc.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. + for (Requirement req + : Util.getDynamicRequirements(wiring.getResourceRequirements(null))) + { + // Get the candidates for the current requirement. + List 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)) + { + String resolution = req.getDirectives() + .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE); + if ((resolution == null) + || !resolution.equals(PackageNamespace.RESOLUTION_DYNAMIC)) + { + // Get the candidates for the current requirement. + List 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); + } + } + } + + // First, add all exported packages to the target resource's package space. + calculateExportedPackages(rc, resource, allCandidates, resourcePkgMap); + Packages 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(rc, cap.getResource(), allCandidates, resourcePkgMap); + mergeCandidatePackages( + rc, resource, req, cap, resourcePkgMap, allCandidates, + new HashMap>()); + } + + // Third, have all candidates to calculate their package spaces. + for (int i = 0; i < caps.size(); i++) + { + calculatePackageSpaces( + rc, 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 blameReqs = new ArrayList(); + blameReqs.add(req); + + mergeUses( + rc, + resource, + resourcePkgs, + cap, + blameReqs, + resourcePkgMap, + allCandidates, + usesCycleMap); + } + } + // Merge uses constraints from imported packages. + for (Entry> entry : resourcePkgs.m_importedPkgs.entrySet()) + { + for (Blame blame : entry.getValue()) + { + // Ignore resources that import from themselves. + if (!blame.m_cap.getResource().equals(resource)) + { + List blameReqs = new ArrayList(); + blameReqs.add(blame.m_reqs.get(0)); + + mergeUses( + rc, + resource, + resourcePkgs, + blame.m_cap, + blameReqs, + resourcePkgMap, + allCandidates, + usesCycleMap); + } + } + } + // Merge uses constraints from required bundles. + for (Entry> entry : resourcePkgs.m_requiredPkgs.entrySet()) + { + for (Blame blame : entry.getValue()) + { + List blameReqs = new ArrayList(); + blameReqs.add(blame.m_reqs.get(0)); + + mergeUses( + rc, + resource, + resourcePkgs, + blame.m_cap, + blameReqs, + resourcePkgMap, + allCandidates, + usesCycleMap); + } + } + } + } + + private void mergeCandidatePackages( + ResolveContext rc, Resource current, Requirement currentReq, + Capability candCap, Map resourcePkgMap, + Candidates allCandidates, Map> cycles) + { + List cycleCaps = cycles.get(current); + if (cycleCaps == null) + { + cycleCaps = new ArrayList(); + 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 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 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 blameReqs = new ArrayList(); + blameReqs.add(currentReq); + + Packages currentPkgs = resourcePkgMap.get(current); + + Map> packages = (requires) + ? currentPkgs.m_requiredPkgs + : currentPkgs.m_importedPkgs; + List blames = packages.get(pkgName); + if (blames == null) + { + blames = new ArrayList(); + packages.put(pkgName, blames); + } + blames.add(new Blame(candCap, blameReqs)); + +//dumpResourcePkgs(current, currentPkgs); + } + } + + private void mergeUses( + ResolveContext rc, Resource current, Packages currentPkgs, + Capability mergeCap, List blameReqs, + Map resourcePkgMap, + Candidates allCandidates, + Map> 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 list = cycleMap.get(mergeCap); + if ((list != null) && list.contains(current)) + { + return; + } + list = (list == null) ? new ArrayList() : list; + list.add(current); + cycleMap.put(mergeCap, list); + + for (Capability candSourceCap : getPackageSources(rc, mergeCap, resourcePkgMap)) + { + List 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 candSourceBlames = null; + // Check to see if the used package is exported. + Blame candExportedBlame = candSourcePkgs.m_exportedPkgs.get(usedPkgName); + if (candExportedBlame != null) + { + candSourceBlames = new ArrayList(1); + candSourceBlames.add(candExportedBlame); + } + else + { + // If the used package is not exported, check to see if it + // is required. + candSourceBlames = candSourcePkgs.m_requiredPkgs.get(usedPkgName); + // Lastly, if the used package is not required, check to see if it + // is imported. + candSourceBlames = (candSourceBlames != null) + ? candSourceBlames : candSourcePkgs.m_importedPkgs.get(usedPkgName); + } + + // If the used package cannot be found, then just ignore it + // since it has no impact. + if (candSourceBlames == null) + { + continue; + } + + List usedCaps = currentPkgs.m_usedPkgs.get(usedPkgName); + if (usedCaps == null) + { + usedCaps = new ArrayList(); + currentPkgs.m_usedPkgs.put(usedPkgName, usedCaps); + } + for (Blame blame : candSourceBlames) + { + if (blame.m_reqs != null) + { + List blameReqs2 = new ArrayList(blameReqs); + blameReqs2.add(blame.m_reqs.get(blame.m_reqs.size() - 1)); + usedCaps.add(new Blame(blame.m_cap, blameReqs2)); + mergeUses(rc, current, currentPkgs, blame.m_cap, blameReqs2, + resourcePkgMap, allCandidates, cycleMap); + } + else + { + usedCaps.add(new Blame(blame.m_cap, blameReqs)); + mergeUses(rc, current, currentPkgs, blame.m_cap, blameReqs, + resourcePkgMap, allCandidates, cycleMap); + } + } + } + } + } + + private void checkPackageSpaceConsistency( + ResolveContext rc, + boolean isDynamicImporting, + Resource resource, + Candidates allCandidates, + Map resourcePkgMap, + Map resultCache) throws ResolutionException + { + if (rc.getWirings().containsKey(resource) && !isDynamicImporting) + { + return; + } + else if(resultCache.containsKey(resource)) + { + return; + } + + Packages pkgs = resourcePkgMap.get(resource); + + ResolutionException rethrow = null; + Candidates permutation = null; + Set mutated = null; + + // Check for conflicting imports from fragments. + for (Entry> 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), m_importPermutations); + // Try to permutate the source requirement. + permutate(allCandidates, sourceBlame.m_reqs.get(0), m_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(rc, allCandidates, sourceBlame) + + "\n\nChain 2:\n" + + toStringBlame(rc, 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 entry : pkgs.m_exportedPkgs.entrySet()) + { + String pkgName = entry.getKey(); + Blame exportBlame = entry.getValue(); + if (!pkgs.m_usedPkgs.containsKey(pkgName)) + { + continue; + } + for (Blame usedBlame : pkgs.m_usedPkgs.get(pkgName)) + { + if (!isCompatible(rc, exportBlame.m_cap, usedBlame.m_cap, resourcePkgMap)) + { + // 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(rc, allCandidates, usedBlame), + null, + null); + + mutated = (mutated != null) + ? mutated + : new HashSet(); + + for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--) + { + Requirement req = usedBlame.m_reqs.get(reqIdx); + + // If we've already permutated this requirement in another + // uses constraint, don't permutate it again just continue + // with the next uses constraint. + if (mutated.contains(req)) + { + break; + } + + // See if we can permutate the candidates for blamed + // requirement; there may be no candidates if the resource + // associated with the requirement is already resolved. + List candidates = permutation.getCandidates(req); + if ((candidates != null) && (candidates.size() > 1)) + { + mutated.add(req); + // Remove the conflicting candidate. + candidates.remove(0); + // Continue with the next uses constraint. + break; + } + } + } + } + + if (rethrow != null) + { + if (mutated.size() > 0) + { + m_usesPermutations.add(permutation); + } + m_logger.log( + Logger.LOG_DEBUG, + "Candidate permutation failed due to a conflict between " + + "an export and import; will try another if possible.", + rethrow); + throw rethrow; + } + } + + // Check if there are any uses conflicts with imported packages. + for (Entry> entry : pkgs.m_importedPkgs.entrySet()) + { + for (Blame importBlame : entry.getValue()) + { + String pkgName = entry.getKey(); + if (!pkgs.m_usedPkgs.containsKey(pkgName)) + { + continue; + } + for (Blame usedBlame : pkgs.m_usedPkgs.get(pkgName)) + { + if (!isCompatible(rc, importBlame.m_cap, usedBlame.m_cap, resourcePkgMap)) + { + // Create a candidate permutation that eliminates any candidates + // that conflict with existing selected candidates. + permutation = (permutation != null) + ? permutation + : allCandidates.copy(); + rethrow = (rethrow != null) + ? rethrow + : new ResolutionException( + "Uses constraint violation. Unable to resolve resource " + + Util.getSymbolicName(resource) + + " [" + resource + + "] because it is exposed to package '" + + pkgName + + "' from resources " + + Util.getSymbolicName(importBlame.m_cap.getResource()) + + " [" + importBlame.m_cap.getResource() + + "] and " + + Util.getSymbolicName(usedBlame.m_cap.getResource()) + + " [" + usedBlame.m_cap.getResource() + + "] via two dependency chains.\n\nChain 1:\n" + + toStringBlame(rc, allCandidates, importBlame) + + "\n\nChain 2:\n" + + toStringBlame(rc, allCandidates, usedBlame), + null, + null); + + mutated = (mutated != null) + ? mutated + : new HashSet(); + + for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--) + { + Requirement req = usedBlame.m_reqs.get(reqIdx); + + // If we've already permutated this requirement in another + // uses constraint, don't permutate it again just continue + // with the next uses constraint. + if (mutated.contains(req)) + { + break; + } + + // See if we can permutate the candidates for blamed + // requirement; there may be no candidates if the resource + // associated with the requirement is already resolved. + List candidates = permutation.getCandidates(req); + if ((candidates != null) && (candidates.size() > 1)) + { + mutated.add(req); + // Remove the conflicting candidate. + candidates.remove(0); + // Continue with the next uses constraint. + break; + } + } + } + } + + // If there was a uses conflict, then we should add a uses + // permutation if we were able to permutate any candidates. + // Additionally, we should try to push an import permutation + // for the original import to force a backtracking on the + // original candidate decision if no viable candidate is found + // for the conflicting uses constraint. + if (rethrow != null) + { + // Add uses permutation if we mutated any candidates. + if (mutated.size() > 0) + { + m_usesPermutations.add(permutation); + } + + // Try to permutate the candidate for the original + // import requirement; only permutate it if we haven't + // done so already. + Requirement req = importBlame.m_reqs.get(0); + if (!mutated.contains(req)) + { + // Since there may be lots of uses constraint violations + // with existing import decisions, we may end up trying + // to permutate the same import a lot of times, so we should + // try to check if that the case and only permutate it once. + permutateIfNeeded(allCandidates, req, m_importPermutations); + } + + m_logger.log( + Logger.LOG_DEBUG, + "Candidate permutation failed due to a conflict between " + + "imports; will try another if possible.", + rethrow); + throw rethrow; + } + } + } + + resultCache.put(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 = m_usesPermutations.size() + m_importPermutations.size(); + for (Entry> entry : pkgs.m_importedPkgs.entrySet()) + { + for (Blame importBlame : entry.getValue()) + { + if (!resource.equals(importBlame.m_cap.getResource())) + { + try + { + checkPackageSpaceConsistency( + rc, false, importBlame.m_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 == (m_usesPermutations.size() + m_importPermutations.size())) + { + Requirement req = importBlame.m_reqs.get(0); + permutate(allCandidates, req, m_importPermutations); + } + throw ex; + } + } + } + } + } + + private static void permutate( + Candidates allCandidates, Requirement req, List permutations) + { + List candidates = allCandidates.getCandidates(req); + if (candidates.size() > 1) + { + Candidates perm = allCandidates.copy(); + candidates = perm.getCandidates(req); + candidates.remove(0); + permutations.add(perm); + } + } + + private static void permutateIfNeeded( + Candidates allCandidates, Requirement req, List permutations) + { + List candidates = allCandidates.getCandidates(req); + if (candidates.size() > 1) + { + // Check existing permutations to make sure we haven't + // already permutated this requirement. This check for + // duplicate permutations is simplistic. It assumes if + // there is any permutation that contains a different + // initial candidate for the requirement in question, + // then it has already been permutated. + boolean permutated = false; + for (Candidates existingPerm : permutations) + { + List existingPermCands = existingPerm.getCandidates(req); + if (!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 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 caps = (wiring != null) + ? wiring.getResourceCapabilities(null) + : resource.getCapabilities(null); + Map exports = new HashMap(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 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 entry : exports.entrySet()) + { + packages.m_exportedPkgs.put( + entry.getKey(), new Blame(entry.getValue(), null)); + } + } + + resourcePkgMap.put(resource, packages); + } + + private boolean isCompatible( + ResolveContext rc, Capability currentCap, Capability candCap, + Map resourcePkgMap) + { + if ((currentCap != null) && (candCap != null)) + { + if (currentCap.equals(candCap)) + { + return true; + } + + List currentSources = + getPackageSources( + rc, + currentCap, + resourcePkgMap); + List candSources = + getPackageSources( + rc, + candCap, + resourcePkgMap); + + return currentSources.containsAll(candSources) + || candSources.containsAll(currentSources); + } + return true; + } + + private Map> m_packageSourcesCache = new HashMap(); + + private List getPackageSources( + ResolveContext rc, Capability cap, Map resourcePkgMap) + { + // If it is a package, then calculate sources for it. + if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)) + { + List sources = m_packageSourcesCache.get(cap); + if (sources == null) + { + sources = getPackageSourcesInternal( + rc, cap, resourcePkgMap, new ArrayList(), new HashSet()); + m_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 getPackageSourcesInternal( + ResolveContext rc, Capability cap, Map resourcePkgMap, + List sources, Set 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 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())) + { + sources.add(new WrappedCapability(cap.getResource(), sourceCap)); + } + else + { + sources.add(sourceCap); + } + } + } + + // Then get any addition sources for the package from required bundles. + Packages pkgs = resourcePkgMap.get(cap.getResource()); + List 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> populateWireMap( + ResolveContext rc, Resource resource, Map resourcePkgMap, + Map> wireMap, Candidates allCandidates) + { + Resource unwrappedResource = getDeclaredResource(resource); + if (!rc.getWirings().containsKey(unwrappedResource) + && !wireMap.containsKey(unwrappedResource)) + { + wireMap.put(unwrappedResource, (List) Collections.EMPTY_LIST); + + List packageWires = new ArrayList(); + List bundleWires = new ArrayList(); + List capabilityWires = new ArrayList(); + + for (Requirement req : resource.getRequirements(null)) + { + List cands = allCandidates.getCandidates(req); + if ((cands != null) && (cands.size() > 0)) + { + Capability cand = cands.get(0); + // Ignore resources that import themselves. + if (!resource.equals(cand.getResource())) + { + if (!rc.getWirings().containsKey(cand.getResource())) + { + populateWireMap(rc, cand.getResource(), + resourcePkgMap, wireMap, allCandidates); + } + Packages candPkgs = resourcePkgMap.get(cand.getResource()); + 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); + } + } + } + } + + // 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 fragments = ((WrappedResource) resource).getFragments(); + for (Resource fragment : fragments) + { + List hostWires = wireMap.get(fragment); + if (hostWires == null) + { + hostWires = new ArrayList(); + wireMap.put(fragment, hostWires); + } + hostWires.add( + new WireImpl( + getDeclaredResource(fragment), + fragment.getRequirements( + HostNamespace.HOST_NAMESPACE).get(0), + unwrappedResource, + unwrappedResource.getCapabilities( + HostNamespace.HOST_NAMESPACE).get(0))); + } + } + } + + return wireMap; + } + + private static Map> populateDynamicWireMap( + ResolveContext rc, Resource resource, Requirement dynReq, + Map resourcePkgMap, + Map> wireMap, Candidates allCandidates) + { + wireMap.put(resource, (List) Collections.EMPTY_LIST); + + List packageWires = new ArrayList(); + + // Get the candidates for the current dynamic requirement. + List 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 resourcePkgMap) + { + System.out.println("+++RESOURCE PKG MAP+++"); + for (Entry 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 entry : packages.m_exportedPkgs.entrySet()) + { + System.out.println(" " + entry.getKey() + " - " + entry.getValue()); + } + System.out.println(" IMPORTED"); + for (Entry> entry : packages.m_importedPkgs.entrySet()) + { + System.out.println(" " + entry.getKey() + " - " + entry.getValue()); + } + System.out.println(" REQUIRED"); + for (Entry> entry : packages.m_requiredPkgs.entrySet()) + { + System.out.println(" " + entry.getKey() + " - " + entry.getValue()); + } + System.out.println(" USED"); + for (Entry> 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("="); + sb.append(export.getAttributes().get(export.getNamespace()).toString()); + 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 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 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 m_exportedPkgs = new HashMap(); + public final Map> m_importedPkgs = new HashMap(); + public final Map> m_requiredPkgs = new HashMap(); + public final Map> m_usedPkgs = new HashMap(); + + public Packages(Resource resource) + { + m_resource = resource; + } + } + + private static class Blame + { + public final Capability m_cap; + public final List m_reqs; + + public Blame(Capability cap, List 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); + } + } +} \ No newline at end of file Added: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/ShadowList.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/ShadowList.java?rev=1328953&view=auto ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/ShadowList.java (added) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/ShadowList.java Sun Apr 22 19:49:20 2012 @@ -0,0 +1,157 @@ +/* + * 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.Iterator; +import java.util.List; +import java.util.ListIterator; + +public class ShadowList implements List +{ + private final List m_original; + private final List m_shadow; + + public ShadowList(List original) + { + m_original = original; + m_shadow = new ArrayList(original); + } + + public List getOriginal() + { + return m_original; + } + + public int size() + { + return m_shadow.size(); + } + + public boolean isEmpty() + { + return m_shadow.isEmpty(); + } + + public boolean contains(Object o) + { + return m_shadow.contains(o); + } + + public Iterator iterator() + { + return m_shadow.iterator(); + } + + public Object[] toArray() + { + return m_shadow.toArray(); + } + + public T[] toArray(T[] ts) + { + return m_shadow.toArray(ts); + } + + public boolean add(T e) + { + return m_shadow.add(e); + } + + public boolean remove(Object o) + { + return m_shadow.remove(o); + } + + public boolean containsAll(Collection clctn) + { + return m_shadow.containsAll(clctn); + } + + public boolean addAll(Collection clctn) + { + return m_shadow.addAll(clctn); + } + + public boolean addAll(int i, Collection clctn) + { + return m_shadow.addAll(i, clctn); + } + + public boolean removeAll(Collection clctn) + { + return m_shadow.removeAll(clctn); + } + + public boolean retainAll(Collection clctn) + { + return m_shadow.retainAll(clctn); + } + + public void clear() + { + m_shadow.clear(); + } + + public T get(int i) + { + return m_shadow.get(i); + } + + public T set(int i, T e) + { + return m_shadow.set(i, e); + } + + public void add(int i, T e) + { + m_shadow.add(i, e); + } + + public T remove(int i) + { + return m_shadow.remove(i); + } + + public int indexOf(Object o) + { + return m_shadow.indexOf(o); + } + + public int lastIndexOf(Object o) + { + return m_shadow.lastIndexOf(o); + } + + public ListIterator listIterator() + { + return m_shadow.listIterator(); + } + + public ListIterator listIterator(int i) + { + return m_shadow.listIterator(i); + } + + public List subList(int i, int i1) + { + return m_shadow.subList(i, i1); + } +} \ No newline at end of file Added: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/SimpleHostedCapability.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/SimpleHostedCapability.java?rev=1328953&view=auto ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/SimpleHostedCapability.java (added) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/SimpleHostedCapability.java Sun Apr 22 19:49:20 2012 @@ -0,0 +1,61 @@ +/* + * 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.Map; +import org.osgi.resource.Capability; +import org.osgi.resource.Resource; +import org.osgi.service.resolver.HostedCapability; + +class SimpleHostedCapability implements HostedCapability +{ + private final Resource m_host; + private final Capability m_cap; + + SimpleHostedCapability(Resource host, Capability cap) + { + m_host = host; + m_cap = cap; + } + + public Resource getResource() + { + return m_host; + } + + public Capability getDeclaredCapability() + { + return m_cap; + } + + public String getNamespace() + { + return m_cap.getNamespace(); + } + + public Map getDirectives() + { + return m_cap.getDirectives(); + } + + public Map getAttributes() + { + return m_cap.getAttributes(); + } +} \ No newline at end of file Added: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/Util.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/Util.java?rev=1328953&view=auto ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/Util.java (added) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/felix/resolver/Util.java Sun Apr 22 19:49:20 2012 @@ -0,0 +1,99 @@ +/* + * 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.List; +import org.osgi.framework.Version; +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; + +public class Util +{ + public static String getSymbolicName(Resource resource) + { + List caps = resource.getCapabilities(null); + for (Capability cap : caps) + { + if (cap.getNamespace().equals(IdentityNamespace.IDENTITY_NAMESPACE)) + { + return cap.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE).toString(); + } + } + return null; + } + + public static Version getVersion(Resource resource) + { + List caps = resource.getCapabilities(null); + for (Capability cap : caps) + { + if (cap.getNamespace().equals(IdentityNamespace.IDENTITY_NAMESPACE)) + { + return (Version) + cap.getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE); + } + } + return null; + } + + public static boolean isFragment(Resource resource) + { + List caps = resource.getCapabilities(null); + for (Capability cap : caps) + { + if (cap.getNamespace().equals(IdentityNamespace.IDENTITY_NAMESPACE)) + { + String type = (String) + cap.getAttributes().get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE); + return (type != null) && type.equals(IdentityNamespace.TYPE_FRAGMENT); + } + } + return false; + } + + public static boolean isOptional(Requirement req) + { + String resolution = req.getDirectives().get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE); + return Namespace.RESOLUTION_OPTIONAL.equalsIgnoreCase(resolution); + } + + public static List getDynamicRequirements(List reqs) + { + List result = new ArrayList(); + if (reqs != null) + { + for (Requirement req : reqs) + { + String resolution = req.getDirectives() + .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE); + if ((resolution != null) + && resolution.equals(PackageNamespace.RESOLUTION_DYNAMIC)) + { + result.add(req); + } + } + } + return result; + } +} \ No newline at end of file