Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSzymon Ptaszkiewicz2015-04-01 17:00:08 +0000
committerSzymon Ptaszkiewicz2015-04-01 17:18:32 +0000
commit5701f8a56f2cd2c2296e44817f9f953425be82d7 (patch)
treed17239fcd8faddd635a1e2fd2ffb8a187a7ac1b2 /bundles/org.eclipse.osgi/felix
parent906b6219c22e8536de3e43c84d59c4aa00083300 (diff)
downloadrt.equinox.framework-5701f8a56f2cd2c2296e44817f9f953425be82d7.tar.gz
rt.equinox.framework-5701f8a56f2cd2c2296e44817f9f953425be82d7.tar.xz
rt.equinox.framework-5701f8a56f2cd2c2296e44817f9f953425be82d7.zip
Bug 463710 - Reduce the number of warnings for org.apache.* classes by using the "Ignore optional compile problems" option
Change-Id: I88e112d854f411eb0c93982108f876c2112697bc Signed-off-by: Szymon Ptaszkiewicz <szymon.ptaszkiewicz@pl.ibm.com>
Diffstat (limited to 'bundles/org.eclipse.osgi/felix')
-rw-r--r--bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java1263
-rw-r--r--bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/FelixResolveContext.java44
-rw-r--r--bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Logger.java127
-rw-r--r--bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java2284
-rw-r--r--bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ShadowList.java157
-rw-r--r--bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/SimpleHostedCapability.java61
-rw-r--r--bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Util.java111
-rw-r--r--bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WireImpl.java116
-rw-r--r--bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WrappedCapability.java114
-rw-r--r--bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WrappedRequirement.java102
-rw-r--r--bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WrappedResource.java127
11 files changed, 4506 insertions, 0 deletions
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java
new file mode 100644
index 000000000..6b946df8e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java
@@ -0,0 +1,1263 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.resolver;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.HostNamespace;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.osgi.resource.Wire;
+import org.osgi.resource.Wiring;
+import org.osgi.service.resolver.HostedCapability;
+import org.osgi.service.resolver.ResolutionException;
+import org.osgi.service.resolver.ResolveContext;
+
+class Candidates
+{
+ public static final int MANDATORY = 0;
+ public static final int OPTIONAL = 1;
+
+ private final Set<Resource> m_mandatoryResources;
+ // Maps a capability to requirements that match it.
+ private final Map<Capability, Set<Requirement>> m_dependentMap;
+ // Maps a requirement to the capability it matches.
+ private final Map<Requirement, List<Capability>> m_candidateMap;
+ // Maps a bundle revision to its associated wrapped revision; this only happens
+ // when a revision being resolved has fragments to attach to it.
+ private final Map<Resource, WrappedResource> m_allWrappedHosts;
+ // Map used when populating candidates to hold intermediate and final results.
+ private final Map<Resource, Object> m_populateResultCache;
+
+ // Flag to signal if fragments are present in the candidate map.
+ private boolean m_fragmentsPresent = false;
+
+ private final Map<Resource, Boolean> m_validOnDemandResources;
+
+ private final Map<Capability, Requirement> m_subtitutableMap;
+
+ /**
+ * Private copy constructor used by the copy() method.
+ *
+ * @param dependentMap the capability dependency map.
+ * @param candidateMap the requirement candidate map.
+ * @param hostFragments the fragment map.
+ * @param wrappedHosts the wrapped hosts map.
+ * @param substitutableMap
+ */
+ private Candidates(
+ Set<Resource> mandatoryResources,
+ Map<Capability, Set<Requirement>> dependentMap,
+ Map<Requirement, List<Capability>> candidateMap,
+ Map<Resource, WrappedResource> wrappedHosts, Map<Resource, Object> populateResultCache,
+ boolean fragmentsPresent,
+ Map<Resource, Boolean> onDemandResources,
+ Map<Capability, Requirement> substitutableMap)
+ {
+ m_mandatoryResources = mandatoryResources;
+ m_dependentMap = dependentMap;
+ m_candidateMap = candidateMap;
+ m_allWrappedHosts = wrappedHosts;
+ m_populateResultCache = populateResultCache;
+ m_fragmentsPresent = fragmentsPresent;
+ m_validOnDemandResources = onDemandResources;
+ m_subtitutableMap = substitutableMap;
+ }
+
+ /**
+ * Constructs an empty Candidates object.
+ */
+ public Candidates(Map<Resource, Boolean> validOnDemandResources)
+ {
+ m_mandatoryResources = new HashSet<Resource>();
+ m_dependentMap = new HashMap<Capability, Set<Requirement>>();
+ m_candidateMap = new HashMap<Requirement, List<Capability>>();
+ m_allWrappedHosts = new HashMap<Resource, WrappedResource>();
+ m_populateResultCache = new HashMap<Resource, Object>();
+ m_validOnDemandResources = validOnDemandResources;
+ m_subtitutableMap = new HashMap<Capability, Requirement>();
+ }
+
+ /**
+ * Populates candidates for the specified revision. How a revision is
+ * resolved depends on its resolution type as follows:
+ * <ul>
+ * <li><tt>MANDATORY</tt> - must resolve and failure to do so throws an
+ * exception.</li>
+ * <li><tt>OPTIONAL</tt> - attempt to resolve, but no exception is thrown if
+ * the resolve fails.</li>
+ * <li><tt>ON_DEMAND</tt> - only resolve on demand; this only applies to
+ * fragments and will only resolve a fragment if its host is already
+ * selected as a candidate.</li>
+ * </ul>
+ *
+ * @param rc the resolve context used for populating the candidates.
+ * @param resource the resource whose candidates should be populated.
+ * @param resolution indicates the resolution type.
+ */
+ public final void populate(
+ ResolveContext rc, Resource resource, int resolution) throws ResolutionException
+ {
+ // Get the current result cache value, to make sure the revision
+ // hasn't already been populated.
+ Object cacheValue = m_populateResultCache.get(resource);
+ // Has been unsuccessfully populated.
+ if (cacheValue instanceof ResolutionException)
+ {
+ return;
+ }
+ // Has been successfully populated.
+ else if (cacheValue instanceof Boolean)
+ {
+ return;
+ }
+
+ // We will always attempt to populate fragments, since this is necessary
+ // for ondemand attaching of fragment. However, we'll only attempt to
+ // populate optional non-fragment revisions if they aren't already
+ // resolved.
+ boolean isFragment = Util.isFragment(resource);
+ if (!isFragment && rc.getWirings().containsKey(resource))
+ {
+ return;
+ }
+
+ if (resolution == MANDATORY)
+ {
+ m_mandatoryResources.add(resource);
+ }
+ try
+ {
+ // Try to populate candidates for the optional revision.
+ populateResource(rc, resource);
+ }
+ catch (ResolutionException ex)
+ {
+ // Only throw an exception if resolution is mandatory.
+ if (resolution == MANDATORY)
+ {
+ throw ex;
+ }
+ }
+ }
+
+ /**
+ * Populates candidates for the specified revision.
+ *
+ * @param state the resolver state used for populating the candidates.
+ * @param revision the revision whose candidates should be populated.
+ */
+// TODO: FELIX3 - Modify to not be recursive.
+ private void populateResource(ResolveContext rc, Resource resource) throws ResolutionException
+ {
+ // Determine if we've already calculated this revision's candidates.
+ // The result cache will have one of three values:
+ // 1. A resolve exception if we've already attempted to populate the
+ // revision's candidates but were unsuccessful.
+ // 2. Boolean.TRUE indicating we've already attempted to populate the
+ // revision's candidates and were successful.
+ // 3. An array containing the cycle count, current map of candidates
+ // for already processed requirements, and a list of remaining
+ // requirements whose candidates still need to be calculated.
+ // For case 1, rethrow the exception. For case 2, simply return immediately.
+ // For case 3, this means we have a cycle so we should continue to populate
+ // the candidates where we left off and not record any results globally
+ // until we've popped completely out of the cycle.
+
+ // Keeps track of the number of times we've reentered this method
+ // for the current revision.
+ Integer cycleCount = null;
+
+ // Keeps track of the candidates we've already calculated for the
+ // current revision's requirements.
+ Map<Requirement, List<Capability>> localCandidateMap = null;
+
+ // Keeps track of the current revision's requirements for which we
+ // haven't yet found candidates.
+ List<Requirement> remainingReqs = null;
+
+ // Get the cache value for the current revision.
+ Object cacheValue = m_populateResultCache.get(resource);
+
+ // This is case 1.
+ if (cacheValue instanceof ResolutionException)
+ {
+ throw (ResolutionException) cacheValue;
+ }
+ // This is case 2.
+ else if (cacheValue instanceof Boolean)
+ {
+ return;
+ }
+ // This is case 3.
+ else if (cacheValue != null)
+ {
+ // Increment and get the cycle count.
+ cycleCount = (Integer) (((Object[]) cacheValue)[0] =
+ new Integer(((Integer) ((Object[]) cacheValue)[0]).intValue() + 1));
+ // Get the already populated candidates.
+ localCandidateMap = (Map) ((Object[]) cacheValue)[1];
+ // Get the remaining requirements.
+ remainingReqs = (List) ((Object[]) cacheValue)[2];
+ }
+
+ // If there is no cache value for the current revision, then this is
+ // the first time we are attempting to populate its candidates, so
+ // do some one-time checks and initialization.
+ if ((remainingReqs == null) && (localCandidateMap == null))
+ {
+ // Record cycle count.
+ cycleCount = new Integer(0);
+
+ // Create a local map for populating candidates first, just in case
+ // the revision is not resolvable.
+ localCandidateMap = new HashMap();
+
+ // Create a modifiable list of the revision's requirements.
+ remainingReqs = new ArrayList(resource.getRequirements(null));
+
+ // Add these value to the result cache so we know we are
+ // in the middle of populating candidates for the current
+ // revision.
+ m_populateResultCache.put(resource,
+ cacheValue = new Object[] { cycleCount, localCandidateMap, remainingReqs });
+ }
+
+ // If we have requirements remaining, then find candidates for them.
+ while (!remainingReqs.isEmpty())
+ {
+ Requirement req = remainingReqs.remove(0);
+
+ // Ignore non-effective and dynamic requirements.
+ String resolution = req.getDirectives()
+ .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
+ if (!rc.isEffective(req)
+ || ((resolution != null)
+ && resolution.equals(PackageNamespace.RESOLUTION_DYNAMIC)))
+ {
+ continue;
+ }
+
+ // Process the candidates, removing any candidates that
+ // cannot resolve.
+ List<Capability> candidates = rc.findProviders(req);
+ ResolutionException rethrow = processCandidates(rc, resource, candidates);
+
+ // First, due to cycles, makes sure we haven't already failed in
+ // a deeper recursion.
+ Object result = m_populateResultCache.get(resource);
+ if (result instanceof ResolutionException)
+ {
+ throw (ResolutionException) result;
+ }
+ // Next, if are no candidates remaining and the requirement is not
+ // not optional, then record and throw a resolve exception.
+ else if (candidates.isEmpty() && !Util.isOptional(req))
+ {
+ if (Util.isFragment(resource) && rc.getWirings().containsKey(resource))
+ {
+ // This is a fragment that is already resolved and there is no unresolved hosts to attach it to.
+ m_populateResultCache.put(resource, Boolean.TRUE);
+ return;
+ }
+ String msg = "Unable to resolve " + resource
+ + ": missing requirement " + req;
+ if (rethrow != null)
+ {
+ msg = msg + " [caused by: " + rethrow.getMessage() + "]";
+ }
+ rethrow = new ResolutionException(msg, null, Collections.singleton(req));
+ m_populateResultCache.put(resource, rethrow);
+ throw rethrow;
+ }
+ // Otherwise, if we actually have candidates for the requirement, then
+ // add them to the local candidate map.
+ else if (candidates.size() > 0)
+ {
+ localCandidateMap.put(req, candidates);
+ }
+ }
+
+ // If we are exiting from a cycle then decrement
+ // cycle counter, otherwise record the result.
+ if (cycleCount.intValue() > 0)
+ {
+ ((Object[]) cacheValue)[0] = new Integer(cycleCount.intValue() - 1);
+ }
+ else if (cycleCount.intValue() == 0)
+ {
+ // Record that the revision was successfully populated.
+ m_populateResultCache.put(resource, Boolean.TRUE);
+ // Merge local candidate map into global candidate map.
+ if (localCandidateMap.size() > 0)
+ {
+ add(localCandidateMap);
+ }
+ if ((rc instanceof FelixResolveContext) && !Util.isFragment(resource))
+ {
+ Collection<Resource> ondemandFragments = ((FelixResolveContext) rc).getOndemandResources(resource);
+ for (Resource fragment : ondemandFragments)
+ {
+ Boolean valid = m_validOnDemandResources.get(fragment);
+ if (valid == null)
+ {
+ // Mark this resource as a valid on demand resource
+ m_validOnDemandResources.put(fragment, Boolean.TRUE);
+ valid = Boolean.TRUE;
+ }
+ if (valid)
+ {
+ // This resource is a valid on demand resource;
+ // populate it now, consider it optional
+ populate(rc, fragment, OPTIONAL);
+ }
+ }
+ }
+ }
+ }
+
+ private void populateSubstitutables()
+ {
+ for (Map.Entry<Resource, Object> populated : m_populateResultCache.entrySet())
+ {
+ if (populated.getValue() instanceof Boolean)
+ {
+ populateSubstitutables(populated.getKey());
+ }
+ }
+ }
+
+ private void populateSubstitutables(Resource resource)
+ {
+ // Collect the package names exported
+ List<Capability> packageExports = resource.getCapabilities(PackageNamespace.PACKAGE_NAMESPACE);
+ if (packageExports.isEmpty())
+ {
+ return;
+ }
+ List<Requirement> packageImports = resource.getRequirements(PackageNamespace.PACKAGE_NAMESPACE);
+ if (packageImports.isEmpty())
+ {
+ return;
+ }
+ Map<String, Collection<Capability>> exportNames = new HashMap<String, Collection<Capability>>();
+ for (Capability packageExport : packageExports)
+ {
+ String packageName = (String) packageExport.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
+ Collection<Capability> caps = exportNames.get(packageName);
+ if (caps == null)
+ {
+ caps = new ArrayList<Capability>(1);
+ exportNames.put(packageName, caps);
+ }
+ caps.add(packageExport);
+ }
+ // Check if any requirements substitute one of the exported packages
+ for (Requirement req : packageImports)
+ {
+ List<Capability> substitutes = m_candidateMap.get(req);
+ if (substitutes != null && !substitutes.isEmpty())
+ {
+ String packageName = (String) substitutes.iterator().next().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
+ Collection<Capability> exportedPackages = exportNames.get(packageName);
+ if (exportedPackages != null)
+ {
+ // The package is exported;
+ // Check if the requirement only has the bundle's own export as candidates
+ substitutes = new ArrayList<Capability>(substitutes);
+ for (Capability exportedPackage : exportedPackages)
+ {
+ substitutes.remove(exportedPackage);
+ }
+ if (!substitutes.isEmpty())
+ {
+ for (Capability exportedPackage : exportedPackages)
+ {
+ m_subtitutableMap.put(exportedPackage, req);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static final int UNPROCESSED = 0;
+ private static final int PROCESSING = 1;
+ private static final int SUBSTITUTED = 2;
+ private static final int EXPORTED = 3;
+
+ void checkSubstitutes(List<Candidates> importPermutations) throws ResolutionException
+ {
+ Map<Capability, Integer> substituteStatuses = new HashMap<Capability, Integer>(m_subtitutableMap.size());
+ for (Capability substitutable : m_subtitutableMap.keySet())
+ {
+ // initialize with unprocessed
+ substituteStatuses.put(substitutable, UNPROCESSED);
+ }
+ // note we are iterating over the original unmodified map by design
+ for (Capability substitutable : m_subtitutableMap.keySet())
+ {
+ isSubstituted(substitutable, substituteStatuses);
+ }
+
+ // Remove any substituted exports from candidates
+ for (Map.Entry<Capability, Integer> substituteStatus : substituteStatuses.entrySet())
+ {
+ if (substituteStatus.getValue() == SUBSTITUTED)
+ {
+ if (m_dependentMap.isEmpty())
+ {
+ // make sure the dependents are populated
+ populateDependents();
+ }
+ }
+ // add a permutation that imports a different candidate for the substituted if possible
+ Requirement substitutedReq = m_subtitutableMap.get(substituteStatus.getKey());
+ if (substitutedReq != null)
+ {
+ ResolverImpl.permutateIfNeeded(this, substitutedReq, importPermutations);
+ }
+ Set<Requirement> dependents = m_dependentMap.get(substituteStatus.getKey());
+ if (dependents != null)
+ {
+ for (Requirement dependent : dependents)
+ {
+ List<Capability> candidates = m_candidateMap.get(dependent);
+ if (candidates != null)
+ {
+ candidates:
+ for (Iterator<Capability> iCandidates = candidates.iterator(); iCandidates.hasNext();)
+ {
+ Capability candidate = iCandidates.next();
+ Integer candidateStatus = substituteStatuses.get(candidate);
+ if (candidateStatus == null)
+ {
+ candidateStatus = EXPORTED;
+ }
+ switch (candidateStatus)
+ {
+ case EXPORTED:
+ // non-substituted candidate hit before the substituted one; do not continue
+ break candidates;
+ case SUBSTITUTED:
+ default:
+ // Need to remove any substituted that comes before an exported candidate
+ iCandidates.remove();
+ // continue to next candidate
+ break;
+ }
+ }
+ if (candidates.isEmpty())
+ {
+ if (Util.isOptional(dependent))
+ {
+ clearCandidates(dependent);
+ }
+ else
+ {
+ String msg = "Unable to resolve " + dependent.getResource()
+ + ": missing requirement " + dependent;
+ throw new ResolutionException(msg, null, Collections.singleton(dependent));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private boolean isSubstituted(Capability substitutableCap, Map<Capability, Integer> substituteStatuses)
+ {
+ Integer substituteState = substituteStatuses.get(substitutableCap);
+ if (substituteState == null)
+ {
+ return false;
+ }
+
+ switch (substituteState.intValue())
+ {
+ case PROCESSING:
+ // found a cycle mark the initiator as not substituted
+ substituteStatuses.put(substitutableCap, EXPORTED);
+ return false;
+ case SUBSTITUTED:
+ return true;
+ case EXPORTED:
+ return false;
+ default:
+ break;
+ }
+
+ Requirement substitutableReq = m_subtitutableMap.get(substitutableCap);
+ if (substitutableReq == null)
+ {
+ // this should never happen.
+ return false;
+ }
+ // mark as processing to detect cycles
+ substituteStatuses.put(substitutableCap, PROCESSING);
+ // discover possible substitutes
+ List<Capability> substitutes = m_candidateMap.get(substitutableReq);
+ if (substitutes != null)
+ {
+ for (Iterator<Capability> iSubstitutes = substitutes.iterator(); iSubstitutes.hasNext();)
+ {
+ Capability substituteCandidate = iSubstitutes.next();
+ if (substituteCandidate.getResource().equals(substitutableCap.getResource()))
+ {
+ substituteStatuses.put(substitutableCap, EXPORTED);
+ return false;
+ }
+ if (!isSubstituted(substituteCandidate, substituteStatuses))
+ {
+ // The resource's exported package is substituted for this permutation.
+ substituteStatuses.put(substitutableCap, SUBSTITUTED);
+ return true;
+ }
+ }
+ }
+ // if we get here then the export is not substituted
+ substituteStatuses.put(substitutableCap, EXPORTED);
+ return false;
+ }
+
+ public void populateDynamic(
+ ResolveContext rc, Resource resource,
+ Requirement req, List<Capability> candidates) throws ResolutionException
+ {
+ // Record the revision associated with the dynamic require
+ // as a mandatory revision.
+ m_mandatoryResources.add(resource);
+
+ // Add the dynamic imports candidates.
+ add(req, candidates);
+
+ // Process the candidates, removing any candidates that
+ // cannot resolve.
+ ResolutionException rethrow = processCandidates(rc, resource, candidates);
+
+ if (candidates.isEmpty())
+ {
+ if (rethrow == null)
+ {
+ rethrow = new ResolutionException(
+ "Dynamic import failed.", null, Collections.singleton(req));
+ }
+ throw rethrow;
+ }
+
+ m_populateResultCache.put(resource, Boolean.TRUE);
+ }
+
+ /**
+ * This method performs common processing on the given set of candidates.
+ * Specifically, it removes any candidates which cannot resolve and it
+ * synthesizes candidates for any candidates coming from any attached
+ * fragments, since fragment capabilities only appear once, but technically
+ * each host represents a unique capability.
+ *
+ * @param state the resolver state.
+ * @param revision the revision being resolved.
+ * @param candidates the candidates to process.
+ * @return a resolve exception to be re-thrown, if any, or null.
+ */
+ private ResolutionException processCandidates(
+ ResolveContext rc,
+ Resource resource,
+ List<Capability> candidates)
+ {
+ // Get satisfying candidates and populate their candidates if necessary.
+ ResolutionException rethrow = null;
+ Set<Capability> fragmentCands = null;
+ for (Iterator<Capability> itCandCap = candidates.iterator();
+ itCandCap.hasNext();)
+ {
+ Capability candCap = itCandCap.next();
+
+ boolean isFragment = Util.isFragment(candCap.getResource());
+
+ // If the capability is from a fragment, then record it
+ // because we have to insert associated host capabilities
+ // if the fragment is already attached to any hosts.
+ if (isFragment)
+ {
+ if (fragmentCands == null)
+ {
+ fragmentCands = new HashSet<Capability>();
+ }
+ fragmentCands.add(candCap);
+ }
+
+ // If the candidate revision is a fragment, then always attempt
+ // to populate candidates for its dependency, since it must be
+ // attached to a host to be used. Otherwise, if the candidate
+ // revision is not already resolved and is not the current version
+ // we are trying to populate, then populate the candidates for
+ // its dependencies as well.
+ // NOTE: Technically, we don't have to check to see if the
+ // candidate revision is equal to the current revision, but this
+ // saves us from recursing and also simplifies exceptions messages
+ // since we effectively chain exception messages for each level
+ // of recursion; thus, any avoided recursion results in fewer
+ // exceptions to chain when an error does occur.
+ if ((isFragment || !rc.getWirings().containsKey(candCap.getResource()))
+ && !candCap.getResource().equals(resource))
+ {
+ try
+ {
+ populateResource(rc, candCap.getResource());
+ }
+ catch (ResolutionException ex)
+ {
+ if (rethrow == null)
+ {
+ rethrow = ex;
+ }
+ // Remove the candidate since we weren't able to
+ // populate its candidates.
+ itCandCap.remove();
+ }
+ }
+ }
+
+ // If any of the candidates for the requirement were from a fragment,
+ // then also insert synthesized hosted capabilities for any other host
+ // to which the fragment is attached since they are all effectively
+ // unique capabilities.
+ if (fragmentCands != null)
+ {
+ for (Capability fragCand : fragmentCands)
+ {
+ String fragCandName = fragCand.getNamespace();
+ if (IdentityNamespace.IDENTITY_NAMESPACE.equals(fragCandName))
+ {
+ // no need to wrap identity namespace ever
+ continue;
+ }
+ // Only necessary for resolved fragments.
+ Wiring wiring = rc.getWirings().get(fragCand.getResource());
+ if (wiring != null)
+ {
+ // Fragments only have host wire, so each wire represents
+ // an attached host.
+ for (Wire wire : wiring.getRequiredResourceWires(HostNamespace.HOST_NAMESPACE))
+ {
+ // If the capability is a package, then make sure the
+ // host actually provides it in its resolved capabilities,
+ // since it may be a substitutable export.
+ if (!fragCandName.equals(PackageNamespace.PACKAGE_NAMESPACE)
+ || rc.getWirings().get(wire.getProvider())
+ .getResourceCapabilities(null).contains(fragCand))
+ {
+ // Note that we can just add this as a candidate
+ // directly, since we know it is already resolved.
+ // NOTE: We are synthesizing a hosted capability here,
+ // but we are not using a ShadowList like we do when
+ // we synthesizing capabilities for unresolved hosts.
+ // It is not necessary to use the ShadowList here since
+ // the host is resolved, because in that case we can
+ // calculate the proper package space by traversing
+ // the wiring. In the unresolved case, this isn't possible
+ // so we need to use the ShadowList so we can keep
+ // a reference to a synthesized resource with attached
+ // fragments so we can correctly calculate its package
+ // space.
+ // Must remove the fragment candidate because we must
+ // only use hosted capabilities for package namespace
+ candidates.remove(fragCand);
+ rc.insertHostedCapability(
+ candidates,
+ new WrappedCapability(
+ wire.getCapability().getResource(),
+ fragCand));
+ }
+ }
+ }
+ }
+ }
+
+ return rethrow;
+ }
+
+ public boolean isPopulated(Resource resource)
+ {
+ Object value = m_populateResultCache.get(resource);
+ return ((value != null) && (value instanceof Boolean));
+ }
+
+ public ResolutionException getResolveException(Resource resource)
+ {
+ Object value = m_populateResultCache.get(resource);
+ return ((value != null) && (value instanceof ResolutionException))
+ ? (ResolutionException) value : null;
+ }
+
+ /**
+ * Adds a requirement and its matching candidates to the internal data
+ * structure. This method assumes it owns the data being passed in and does
+ * not make a copy. It takes the data and processes, such as calculating
+ * which requirements depend on which capabilities and recording any
+ * fragments it finds for future merging.
+ *
+ * @param req the requirement to add.
+ * @param candidates the candidates matching the requirement.
+ */
+ private void add(Requirement req, List<Capability> candidates)
+ {
+ if (req.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
+ {
+ m_fragmentsPresent = true;
+ }
+
+ // Record the candidates.
+ m_candidateMap.put(req, candidates);
+ }
+
+ /**
+ * Adds requirements and candidates in bulk. The outer map is not retained
+ * by this method, but the inner data structures are, so they should not be
+ * further modified by the caller.
+ *
+ * @param candidates the bulk requirements and candidates to add.
+ */
+ private void add(Map<Requirement, List<Capability>> candidates)
+ {
+ for (Entry<Requirement, List<Capability>> entry : candidates.entrySet())
+ {
+ add(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * Returns the wrapped resource associated with the given resource. If the
+ * resource was not wrapped, then the resource itself is returned. This is
+ * really only needed to determine if the root resources of the resolve have
+ * been wrapped.
+ *
+ * @param r the resource whose wrapper is desired.
+ * @return the wrapper resource or the resource itself if it was not
+ * wrapped.
+ */
+ public Resource getWrappedHost(Resource r)
+ {
+ Resource wrapped = m_allWrappedHosts.get(r);
+ return (wrapped == null) ? r : wrapped;
+ }
+
+ /**
+ * Gets the candidates associated with a given requirement.
+ *
+ * @param req the requirement whose candidates are desired.
+ * @return the matching candidates or null.
+ */
+ public List<Capability> getCandidates(Requirement req)
+ {
+ return m_candidateMap.get(req);
+ }
+
+ public void clearCandidates(Requirement req)
+ {
+ m_candidateMap.remove(req);
+ }
+
+ /**
+ * Merges fragments into their hosts. It does this by wrapping all host
+ * modules and attaching their selected fragments, removing all unselected
+ * fragment modules, and replacing all occurrences of the original fragments
+ * in the internal data structures with the wrapped host modules instead.
+ * Thus, fragment capabilities and requirements are merged into the
+ * appropriate host and the candidates for the fragment now become
+ * candidates for the host. Likewise, any module depending on a fragment now
+ * depend on the host. Note that this process is sort of like
+ * multiplication, since one fragment that can attach to two hosts
+ * effectively gets multiplied across the two hosts. So, any modules being
+ * satisfied by the fragment will end up having the two hosts as potential
+ * candidates, rather than the single fragment.
+ *
+ * @throws ResolutionException if the removal of any unselected fragments
+ * result in the root module being unable to resolve.
+ */
+ public void prepare(ResolveContext rc) throws ResolutionException
+ {
+ // Maps a host capability to a map containing its potential fragments;
+ // the fragment map maps a fragment symbolic name to a map that maps
+ // a version to a list of fragments requirements matching that symbolic
+ // name and version.
+ Map<Capability, Map<String, Map<Version, List<Requirement>>>> hostFragments = Collections.EMPTY_MAP;
+ if (m_fragmentsPresent)
+ {
+ hostFragments = populateDependents();
+ }
+
+ // This method performs the following steps:
+ // 1. Select the fragments to attach to a given host.
+ // 2. Wrap hosts and attach fragments.
+ // 3. Remove any unselected fragments. This is necessary because
+ // other revisions may depend on the capabilities of unselected
+ // fragments, so we need to remove the unselected fragments and
+ // any revisions that depends on them, which could ultimately cause
+ // the entire resolve to fail.
+ // 4. Replace all fragments with any host it was merged into
+ // (effectively multiplying it).
+ // * This includes setting candidates for attached fragment
+ // requirements as well as replacing fragment capabilities
+ // with host's attached fragment capabilities.
+ // Steps 1 and 2
+ List<WrappedResource> hostResources = new ArrayList<WrappedResource>();
+ List<Resource> unselectedFragments = new ArrayList<Resource>();
+ for (Entry<Capability, Map<String, Map<Version, List<Requirement>>>> hostEntry : hostFragments.entrySet())
+ {
+ // Step 1
+ Capability hostCap = hostEntry.getKey();
+ Map<String, Map<Version, List<Requirement>>> fragments =
+ hostEntry.getValue();
+ List<Resource> selectedFragments = new ArrayList<Resource>();
+ for (Entry<String, Map<Version, List<Requirement>>> fragEntry
+ : fragments.entrySet())
+ {
+ boolean isFirst = true;
+ for (Entry<Version, List<Requirement>> versionEntry
+ : fragEntry.getValue().entrySet())
+ {
+ for (Requirement hostReq : versionEntry.getValue())
+ {
+ // Selecting the first fragment in each entry, which
+ // is equivalent to selecting the highest version of
+ // each fragment with a given symbolic name.
+ if (isFirst)
+ {
+ selectedFragments.add(hostReq.getResource());
+ isFirst = false;
+ }
+ // For any fragment that wasn't selected, remove the
+ // current host as a potential host for it and remove it
+ // as a dependent on the host. If there are no more
+ // potential hosts for the fragment, then mark it as
+ // unselected for later removal.
+ else
+ {
+ m_dependentMap.get(hostCap).remove(hostReq);
+ List<Capability> hosts = m_candidateMap.get(hostReq);
+ hosts.remove(hostCap);
+ if (hosts.isEmpty())
+ {
+ unselectedFragments.add(hostReq.getResource());
+ }
+ }
+ }
+ }
+ }
+
+ // Step 2
+ WrappedResource wrappedHost =
+ new WrappedResource(hostCap.getResource(), selectedFragments);
+ hostResources.add(wrappedHost);
+ m_allWrappedHosts.put(hostCap.getResource(), wrappedHost);
+ }
+
+ // Step 3
+ for (Resource fragment : unselectedFragments)
+ {
+ removeResource(fragment,
+ new ResolutionException(
+ "Fragment was not selected for attachment: " + fragment));
+ }
+
+ // Step 4
+ for (WrappedResource hostResource : hostResources)
+ {
+ // Replaces capabilities from fragments with the capabilities
+ // from the merged host.
+ for (Capability c : hostResource.getCapabilities(null))
+ {
+ // Don't replace the host capability, since the fragment will
+ // really be attached to the original host, not the wrapper.
+ if (!c.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
+ {
+ Capability origCap = ((HostedCapability) c).getDeclaredCapability();
+ // Note that you might think we could remove the original cap
+ // from the dependent map, but you can't since it may come from
+ // a fragment that is attached to multiple hosts, so each host
+ // will need to make their own copy.
+ Set<Requirement> dependents = m_dependentMap.get(origCap);
+ if (dependents != null)
+ {
+ dependents = new HashSet<Requirement>(dependents);
+ m_dependentMap.put(c, dependents);
+ for (Requirement r : dependents)
+ {
+ // We have synthesized hosted capabilities for all
+ // fragments that have been attached to hosts by
+ // wrapping the host bundle and their attached
+ // fragments. We need to use the ResolveContext to
+ // determine the proper priority order for hosted
+ // capabilities since the order may depend on the
+ // declaring host/fragment combination. However,
+ // internally we completely wrap the host revision
+ // and make all capabilities/requirements point back
+ // to the wrapped host not the declaring host. The
+ // ResolveContext expects HostedCapabilities to point
+ // to the declaring revision, so we need two separate
+ // candidate lists: one for the ResolveContext with
+ // HostedCapabilities pointing back to the declaring
+ // host and one for the resolver with HostedCapabilities
+ // pointing back to the wrapped host. We ask the
+ // ResolveContext to insert its appropriate HostedCapability
+ // into its list, then we mirror the insert into a
+ // shadow list with the resolver's HostedCapability.
+ // We only need to ask the ResolveContext to find
+ // the insert position for fragment caps since these
+ // were synthesized and we don't know their priority.
+ // However, in the resolver's candidate list we need
+ // to replace all caps with the wrapped caps, no
+ // matter if they come from the host or fragment,
+ // since we are completing replacing the declaring
+ // host and fragments with the wrapped host.
+ List<Capability> cands = m_candidateMap.get(r);
+ if (!(cands instanceof ShadowList))
+ {
+ ShadowList<Capability> shadow =
+ new ShadowList<Capability>(cands);
+ m_candidateMap.put(r, shadow);
+ cands = shadow;
+ }
+
+ // If the original capability is from a fragment, then
+ // ask the ResolveContext to insert it and update the
+ // shadow copy of the list accordingly.
+ if (!origCap.getResource().equals(hostResource.getDeclaredResource()))
+ {
+ List<Capability> original = ((ShadowList) cands).getOriginal();
+ int removeIdx = original.indexOf(origCap);
+ if (removeIdx != -1)
+ {
+ original.remove(removeIdx);
+ cands.remove(removeIdx);
+ }
+ int insertIdx = rc.insertHostedCapability(
+ original,
+ new SimpleHostedCapability(
+ hostResource.getDeclaredResource(),
+ origCap));
+ cands.add(insertIdx, c);
+ }
+ // If the original capability is from the host, then
+ // we just need to replace it in the shadow list.
+ else
+ {
+ int idx = cands.indexOf(origCap);
+ cands.set(idx, c);
+ }
+ }
+ }
+ }
+ }
+
+ // Copy candidates for fragment requirements to the host.
+ for (Requirement r : hostResource.getRequirements(null))
+ {
+ Requirement origReq = ((WrappedRequirement) r).getDeclaredRequirement();
+ List<Capability> cands = m_candidateMap.get(origReq);
+ if (cands != null)
+ {
+ m_candidateMap.put(r, new ArrayList<Capability>(cands));
+ for (Capability cand : cands)
+ {
+ Set<Requirement> dependents = m_dependentMap.get(cand);
+ dependents.remove(origReq);
+ dependents.add(r);
+ }
+ }
+ }
+ }
+
+ // Lastly, verify that all mandatory revisions are still
+ // populated, since some might have become unresolved after
+ // selecting fragments/singletons.
+ for (Resource resource : m_mandatoryResources)
+ {
+ if (!isPopulated(resource))
+ {
+ throw getResolveException(resource);
+ }
+ }
+
+ populateSubstitutables();
+ }
+
+ // Maps a host capability to a map containing its potential fragments;
+ // the fragment map maps a fragment symbolic name to a map that maps
+ // a version to a list of fragments requirements matching that symbolic
+ // name and version.
+ private Map<Capability, Map<String, Map<Version, List<Requirement>>>> populateDependents()
+ {
+ Map<Capability, Map<String, Map<Version, List<Requirement>>>> hostFragments =
+ new HashMap<Capability, Map<String, Map<Version, List<Requirement>>>>();
+ for (Entry<Requirement, List<Capability>> entry : m_candidateMap.entrySet())
+ {
+ Requirement req = entry.getKey();
+ List<Capability> caps = entry.getValue();
+ for (Capability cap : caps)
+ {
+ // Record the requirement as dependent on the capability.
+ Set<Requirement> dependents = m_dependentMap.get(cap);
+ if (dependents == null)
+ {
+ dependents = new HashSet<Requirement>();
+ m_dependentMap.put(cap, dependents);
+ }
+ dependents.add(req);
+
+ // Keep track of hosts and associated fragments.
+ if (req.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
+ {
+ String resSymName = Util.getSymbolicName(req.getResource());
+ Version resVersion = Util.getVersion(req.getResource());
+
+ Map<String, Map<Version, List<Requirement>>> fragments = hostFragments.get(cap);
+ if (fragments == null)
+ {
+ fragments = new HashMap<String, Map<Version, List<Requirement>>>();
+ hostFragments.put(cap, fragments);
+ }
+ Map<Version, List<Requirement>> fragmentVersions = fragments.get(resSymName);
+ if (fragmentVersions == null)
+ {
+ fragmentVersions =
+ new TreeMap<Version, List<Requirement>>(Collections.reverseOrder());
+ fragments.put(resSymName, fragmentVersions);
+ }
+ List<Requirement> actual = fragmentVersions.get(resVersion);
+ if (actual == null)
+ {
+ actual = new ArrayList<Requirement>();
+ if (resVersion == null)
+ resVersion = new Version(0, 0, 0);
+ fragmentVersions.put(resVersion, actual);
+ }
+ actual.add(req);
+ }
+ }
+ }
+
+ return hostFragments;
+ }
+
+ /**
+ * Removes a module from the internal data structures if it wasn't selected
+ * as a fragment or a singleton. This process may cause other modules to
+ * become unresolved if they depended on the module's capabilities and there
+ * is no other candidate.
+ *
+ * @param revision the module to remove.
+ * @throws ResolveException if removing the module caused the resolve to
+ * fail.
+ */
+ private void removeResource(Resource resource, ResolutionException ex)
+ throws ResolutionException
+ {
+ // Add removal reason to result cache.
+ m_populateResultCache.put(resource, ex);
+ // Remove from dependents.
+ Set<Resource> unresolvedResources = new HashSet<Resource>();
+ remove(resource, unresolvedResources);
+ // Remove dependents that failed as a result of removing revision.
+ while (!unresolvedResources.isEmpty())
+ {
+ Iterator<Resource> it = unresolvedResources.iterator();
+ resource = it.next();
+ it.remove();
+ remove(resource, unresolvedResources);
+ }
+ }
+
+ /**
+ * Removes the specified module from the internal data structures, which
+ * involves removing its requirements and its capabilities. This may cause
+ * other modules to become unresolved as a result.
+ *
+ * @param br the module to remove.
+ * @param unresolvedRevisions a list to containing any additional modules
+ * that that became unresolved as a result of removing this module and will
+ * also need to be removed.
+ * @throws ResolveException if removing the module caused the resolve to
+ * fail.
+ */
+ private void remove(Resource resource, Set<Resource> unresolvedResources)
+ throws ResolutionException
+ {
+ for (Requirement r : resource.getRequirements(null))
+ {
+ remove(r);
+ }
+
+ for (Capability c : resource.getCapabilities(null))
+ {
+ remove(c, unresolvedResources);
+ }
+ }
+
+ /**
+ * Removes a requirement from the internal data structures.
+ *
+ * @param req the requirement to remove.
+ */
+ private void remove(Requirement req)
+ {
+ boolean isFragment = req.getNamespace().equals(HostNamespace.HOST_NAMESPACE);
+
+ List<Capability> candidates = m_candidateMap.remove(req);
+ if (candidates != null)
+ {
+ for (Capability cap : candidates)
+ {
+ Set<Requirement> dependents = m_dependentMap.get(cap);
+ if (dependents != null)
+ {
+ dependents.remove(req);
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a capability from the internal data structures. This may cause
+ * other modules to become unresolved as a result.
+ *
+ * @param c the capability to remove.
+ * @param unresolvedRevisions a list to containing any additional modules
+ * that that became unresolved as a result of removing this module and will
+ * also need to be removed.
+ * @throws ResolveException if removing the module caused the resolve to
+ * fail.
+ */
+ private void remove(Capability c, Set<Resource> unresolvedResources)
+ throws ResolutionException
+ {
+ Set<Requirement> dependents = m_dependentMap.remove(c);
+ if (dependents != null)
+ {
+ for (Requirement r : dependents)
+ {
+ List<Capability> candidates = m_candidateMap.get(r);
+ candidates.remove(c);
+ if (candidates.isEmpty())
+ {
+ m_candidateMap.remove(r);
+ if (!Util.isOptional(r))
+ {
+ String msg = "Unable to resolve " + r.getResource()
+ + ": missing requirement " + r;
+ m_populateResultCache.put(
+ r.getResource(),
+ new ResolutionException(msg, null, Collections.singleton(r)));
+ unresolvedResources.add(r.getResource());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a copy of the Candidates object. This is used for creating
+ * permutations when package space conflicts are discovered.
+ *
+ * @return copy of this Candidates object.
+ */
+ public Candidates copy()
+ {
+ Map<Capability, Set<Requirement>> dependentMap =
+ new HashMap<Capability, Set<Requirement>>();
+ for (Entry<Capability, Set<Requirement>> entry : m_dependentMap.entrySet())
+ {
+ Set<Requirement> dependents = new HashSet<Requirement>(entry.getValue());
+ dependentMap.put(entry.getKey(), dependents);
+ }
+
+ Map<Requirement, List<Capability>> candidateMap =
+ new HashMap<Requirement, List<Capability>>();
+ for (Entry<Requirement, List<Capability>> entry
+ : m_candidateMap.entrySet())
+ {
+ List<Capability> candidates =
+ new ArrayList<Capability>(entry.getValue());
+ candidateMap.put(entry.getKey(), candidates);
+ }
+
+ return new Candidates(
+ m_mandatoryResources, dependentMap, candidateMap,
+ m_allWrappedHosts, m_populateResultCache, m_fragmentsPresent, m_validOnDemandResources,
+ m_subtitutableMap);
+ }
+
+ public void dump(ResolveContext rc)
+ {
+ // Create set of all revisions from requirements.
+ Set<Resource> resources = new HashSet<Resource>();
+ for (Entry<Requirement, List<Capability>> entry
+ : m_candidateMap.entrySet())
+ {
+ resources.add(entry.getKey().getResource());
+ }
+ // Now dump the revisions.
+ System.out.println("=== BEGIN CANDIDATE MAP ===");
+ for (Resource resource : resources)
+ {
+ Wiring wiring = rc.getWirings().get(resource);
+ System.out.println(" " + resource
+ + " (" + ((wiring != null) ? "RESOLVED)" : "UNRESOLVED)"));
+ List<Requirement> reqs = (wiring != null)
+ ? wiring.getResourceRequirements(null)
+ : resource.getRequirements(null);
+ for (Requirement req : reqs)
+ {
+ List<Capability> candidates = m_candidateMap.get(req);
+ if ((candidates != null) && (candidates.size() > 0))
+ {
+ System.out.println(" " + req + ": " + candidates);
+ }
+ }
+ reqs = (wiring != null)
+ ? Util.getDynamicRequirements(wiring.getResourceRequirements(null))
+ : Util.getDynamicRequirements(resource.getRequirements(null));
+ for (Requirement req : reqs)
+ {
+ List<Capability> candidates = m_candidateMap.get(req);
+ if ((candidates != null) && (candidates.size() > 0))
+ {
+ System.out.println(" " + req + ": " + candidates);
+ }
+ }
+ }
+ System.out.println("=== END CANDIDATE MAP ===");
+ }
+}
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/FelixResolveContext.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/FelixResolveContext.java
new file mode 100644
index 000000000..502a7e459
--- /dev/null
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/FelixResolveContext.java
@@ -0,0 +1,44 @@
+/*
+ * 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.Collection;
+import org.osgi.resource.Resource;
+
+public interface FelixResolveContext
+{
+ /**
+ * Return the resources that the resolver should attempt to resolve on
+ * demand for specified resource which is being resolved. Inability to
+ * resolve one of the on demand resources will not result in a resolution
+ * exception.
+ *
+ * <p>
+ * The resolver will ask for on demand resources for each resource that is
+ * getting pulled into a resolve operation. An example of an on demand
+ * resource is a fragment. When a host is being resolved the resolve context
+ * will be asked if any additional resources should be added to the resolve
+ * operation. The resolve context may decide that the potential fragments of
+ * the host should be resolved along with the host.
+ *
+ * @return A collection of the resources that the resolver should attempt to
+ * resolve for this resolve context. May be empty if there are no on demand
+ * resources. The returned collection may be unmodifiable.
+ */
+ public Collection<Resource> getOndemandResources(Resource host);
+}
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Logger.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Logger.java
new file mode 100644
index 000000000..fd3a8a751
--- /dev/null
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Logger.java
@@ -0,0 +1,127 @@
+/*
+ * 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 org.osgi.resource.Resource;
+
+import org.osgi.service.resolver.ResolutionException;
+
+/**
+ * <p>
+ * This class mimics the standard OSGi <tt>LogService</tt> interface. An
+ * instance of this class is used by the framework for all logging. By default
+ * this class logs messages to standard out. The log level can be set to control
+ * the amount of logging performed, where a higher number results in more
+ * logging. A log level of zero turns off logging completely.
+ * </p>
+ * <p>
+ * The log levels match those specified in the OSGi Log Service (i.e., 1 =
+ * error, 2 = warning, 3 = information, and 4 = debug). The default value is 1.
+ * </p>
+ * <p>
+ * This class also uses the System Bundle's context to track log services and
+ * will use the highest ranking log service, if present, as a back end instead
+ * of printing to standard out. The class uses reflection to invoking the log
+ * service's method to avoid a dependency on the log interface.
+ * </p>
+ */
+public class Logger
+{
+ public static final int LOG_ERROR = 1;
+ public static final int LOG_WARNING = 2;
+ public static final int LOG_INFO = 3;
+ public static final int LOG_DEBUG = 4;
+
+ private int m_logLevel = 1;
+
+ public Logger(int i)
+ {
+ m_logLevel = i;
+ }
+
+ public final synchronized void setLogLevel(int i)
+ {
+ m_logLevel = i;
+ }
+
+ public final synchronized int getLogLevel()
+ {
+ return m_logLevel;
+ }
+
+ public final void log(int level, String msg)
+ {
+ _log(level, msg, null);
+ }
+
+ public final void log(int level, String msg, Throwable throwable)
+ {
+ _log(level, msg, throwable);
+ }
+
+ protected void doLog(int level, String msg, Throwable throwable)
+ {
+ if (level > m_logLevel)
+ {
+ return;
+ }
+ String s = "";
+ s = s + msg;
+ if (throwable != null)
+ {
+ s = s + " (" + throwable + ")";
+ }
+ switch (level)
+ {
+ case LOG_DEBUG:
+ System.out.println("DEBUG: " + s);
+ break;
+ case LOG_ERROR:
+ System.out.println("ERROR: " + s);
+ if (throwable != null)
+ {
+ throwable.printStackTrace();
+ }
+ break;
+ case LOG_INFO:
+ System.out.println("INFO: " + s);
+ break;
+ case LOG_WARNING:
+ System.out.println("WARNING: " + s);
+ break;
+ default:
+ System.out.println("UNKNOWN[" + level + "]: " + s);
+ }
+ }
+
+ private void _log(
+ int level,
+ String msg, Throwable throwable)
+ {
+ if (m_logLevel >= level)
+ {
+ doLog(level, msg, throwable);
+ }
+ }
+
+ public void logUsesConstraintViolation(Resource resource, ResolutionException error)
+ {
+ // do nothing by default
+ }
+}
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java
new file mode 100644
index 000000000..21721006d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java
@@ -0,0 +1,2284 @@
+/*
+ * 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>>(), new HashMap<Resource, List<Resource>>());
+ }
+
+ // 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, HashMap<Resource, List<Resource>> visitedRequiredBundlesMap)
+ {
+ 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());
+
+ List<Resource> visitedRequiredBundles = visitedRequiredBundlesMap.get(current);
+ if (visitedRequiredBundles == null)
+ {
+ visitedRequiredBundles = new ArrayList<Resource>();
+ visitedRequiredBundlesMap.put(current, visitedRequiredBundles);
+ }
+ if (!visitedRequiredBundles.contains(candCap.getResource()))
+ {
+ visitedRequiredBundles.add(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, visitedRequiredBundlesMap);
+ }
+ }
+ }
+ }
+ 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, visitedRequiredBundlesMap);
+ }
+ }
+ }
+ }
+ }
+
+ 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();
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ShadowList.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ShadowList.java
new file mode 100644
index 000000000..05734da46
--- /dev/null
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ShadowList.java
@@ -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<T> implements List<T>
+{
+ private final List<T> m_original;
+ private final List<T> m_shadow;
+
+ public ShadowList(List<T> original)
+ {
+ m_original = original;
+ m_shadow = new ArrayList<T>(original);
+ }
+
+ public List<T> 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<T> iterator()
+ {
+ return m_shadow.iterator();
+ }
+
+ public Object[] toArray()
+ {
+ return m_shadow.toArray();
+ }
+
+ public <T> 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<? extends T> clctn)
+ {
+ return m_shadow.addAll(clctn);
+ }
+
+ public boolean addAll(int i, Collection<? extends T> 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<T> listIterator()
+ {
+ return m_shadow.listIterator();
+ }
+
+ public ListIterator<T> listIterator(int i)
+ {
+ return m_shadow.listIterator(i);
+ }
+
+ public List<T> subList(int i, int i1)
+ {
+ return m_shadow.subList(i, i1);
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/SimpleHostedCapability.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/SimpleHostedCapability.java
new file mode 100644
index 000000000..98e2d4f3d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/SimpleHostedCapability.java
@@ -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<String, String> getDirectives()
+ {
+ return m_cap.getDirectives();
+ }
+
+ public Map<String, Object> getAttributes()
+ {
+ return m_cap.getAttributes();
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Util.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Util.java
new file mode 100644
index 000000000..84de220db
--- /dev/null
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Util.java
@@ -0,0 +1,111 @@
+/*
+ * 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<Capability> 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<Capability> 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<Capability> 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 boolean isMultiple(Requirement req)
+ {
+ return Namespace.CARDINALITY_MULTIPLE.equals(req.getDirectives()
+ .get(Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE)) && !isDynamic(req);
+ }
+
+ public static boolean isDynamic(Requirement req)
+ {
+ return PackageNamespace.RESOLUTION_DYNAMIC.equals(req.getDirectives()
+ .get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE));
+ }
+
+ public static List<Requirement> getDynamicRequirements(List<Requirement> reqs)
+ {
+ List<Requirement> result = new ArrayList<Requirement>();
+ 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
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WireImpl.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WireImpl.java
new file mode 100644
index 000000000..a79ac7114
--- /dev/null
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WireImpl.java
@@ -0,0 +1,116 @@
+/*
+ * 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 org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.osgi.resource.Wire;
+
+class WireImpl implements Wire
+{
+ private final Resource m_requirer;
+ private final Requirement m_req;
+ private final Resource m_provider;
+ private final Capability m_cap;
+
+ public WireImpl(
+ Resource requirer, Requirement req,
+ Resource provider, Capability cap)
+ {
+ m_requirer = requirer;
+ m_req = req;
+ m_provider = provider;
+ m_cap = cap;
+ }
+
+ public Resource getRequirer()
+ {
+ return m_requirer;
+ }
+
+ public Requirement getRequirement()
+ {
+ return m_req;
+ }
+
+ public Resource getProvider()
+ {
+ return m_provider;
+ }
+
+ public Capability getCapability()
+ {
+ return m_cap;
+ }
+
+ @Override
+ public String toString()
+ {
+ return m_req
+ + " -> "
+ + "[" + m_provider + "]";
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+ if (!(obj instanceof Wire))
+ {
+ return false;
+ }
+ final Wire other = (Wire) obj;
+ if (this.m_requirer != other.getRequirer()
+ && (this.m_requirer == null || !this.m_requirer.equals(other.getRequirer())))
+ {
+ return false;
+ }
+ if (this.m_req != other.getRequirement()
+ && (this.m_req == null || !this.m_req.equals(other.getRequirement())))
+ {
+ return false;
+ }
+ if (this.m_provider != other.getProvider()
+ && (this.m_provider == null || !this.m_provider.equals(other.getProvider())))
+ {
+ return false;
+ }
+ if (this.m_cap != other.getCapability()
+ && (this.m_cap == null || !this.m_cap.equals(other.getCapability())))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 5;
+ hash = 29 * hash + (this.m_requirer != null ? this.m_requirer.hashCode() : 0);
+ hash = 29 * hash + (this.m_req != null ? this.m_req.hashCode() : 0);
+ hash = 29 * hash + (this.m_provider != null ? this.m_provider.hashCode() : 0);
+ hash = 29 * hash + (this.m_cap != null ? this.m_cap.hashCode() : 0);
+ return hash;
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WrappedCapability.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WrappedCapability.java
new file mode 100644
index 000000000..9bbec36e2
--- /dev/null
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WrappedCapability.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) OSGi Alliance (2012). All Rights Reserved.
+ *
+ * Licensed 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.framework.namespace.PackageNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Resource;
+import org.osgi.service.resolver.HostedCapability;
+
+public class WrappedCapability implements HostedCapability
+{
+ private final Resource m_host;
+ private final Capability m_cap;
+
+ public WrappedCapability(Resource host, Capability cap)
+ {
+ m_host = host;
+ m_cap = cap;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ final WrappedCapability other = (WrappedCapability) obj;
+ if (m_host != other.m_host && (m_host == null || !m_host.equals(other.m_host)))
+ {
+ return false;
+ }
+ if (m_cap != other.m_cap && (m_cap == null || !m_cap.equals(other.m_cap)))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 7;
+ hash = 37 * hash + (m_host != null ? m_host.hashCode() : 0);
+ hash = 37 * hash + (m_cap != null ? m_cap.hashCode() : 0);
+ return hash;
+ }
+
+ public Capability getDeclaredCapability()
+ {
+ return m_cap;
+ }
+
+ public Resource getResource()
+ {
+ return m_host;
+ }
+
+ public String getNamespace()
+ {
+ return m_cap.getNamespace();
+ }
+
+ public Map<String, String> getDirectives()
+ {
+ return m_cap.getDirectives();
+ }
+
+ public Map<String, Object> getAttributes()
+ {
+ return m_cap.getAttributes();
+ }
+
+// TODO: RFC-112 - Need impl-specific type.
+// public List<String> getUses()
+// {
+// return m_cap.getUses();
+// }
+
+ @Override
+ public String toString()
+ {
+ if (m_host == null)
+ {
+ return getAttributes().toString();
+ }
+ if (getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ return "[" + m_host + "] "
+ + getNamespace()
+ + "; "
+ + getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
+ }
+ return "[" + m_host + "] " + getNamespace() + "; " + getAttributes();
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WrappedRequirement.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WrappedRequirement.java
new file mode 100644
index 000000000..a9d66dfea
--- /dev/null
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WrappedRequirement.java
@@ -0,0 +1,102 @@
+/*
+ * 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.Namespace;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+public class WrappedRequirement implements Requirement
+{
+ private final Resource m_host;
+ private final Requirement m_req;
+
+ public WrappedRequirement(Resource host, Requirement req)
+ {
+ m_host = host;
+ m_req = req;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ final WrappedRequirement other = (WrappedRequirement) obj;
+ if (m_host != other.m_host && (m_host == null || !m_host.equals(other.m_host)))
+ {
+ return false;
+ }
+ if (m_req != other.m_req && (m_req == null || !m_req.equals(other.m_req)))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 7;
+ hash = 37 * hash + (m_host != null ? m_host.hashCode() : 0);
+ hash = 37 * hash + (m_req != null ? m_req.hashCode() : 0);
+ return hash;
+ }
+
+ public Requirement getDeclaredRequirement()
+ {
+ return m_req;
+ }
+
+ public Resource getResource()
+ {
+ return m_host;
+ }
+
+ public String getNamespace()
+ {
+ return m_req.getNamespace();
+ }
+
+ public Map<String, String> getDirectives()
+ {
+ return m_req.getDirectives();
+ }
+
+ public Map<String, Object> getAttributes()
+ {
+ return m_req.getAttributes();
+ }
+
+ @Override
+ public String toString()
+ {
+ return "[" + m_host + "] "
+ + getNamespace()
+ + "; "
+ + getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WrappedResource.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WrappedResource.java
new file mode 100644
index 000000000..e37d23458
--- /dev/null
+++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/WrappedResource.java
@@ -0,0 +1,127 @@
+/*
+ * 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.Collections;
+import java.util.List;
+import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
+import org.osgi.framework.namespace.HostNamespace;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+class WrappedResource implements Resource
+{
+ private final Resource m_host;
+ private final List<Resource> m_fragments;
+ private List<Capability> m_cachedCapabilities = null;
+ private List<Requirement> m_cachedRequirements = null;
+
+ public WrappedResource(Resource host, List<Resource> fragments)
+ {
+ m_host = host;
+ m_fragments = fragments;
+ }
+
+ public Resource getDeclaredResource()
+ {
+ return m_host;
+ }
+
+ public List<Resource> getFragments()
+ {
+ return m_fragments;
+ }
+
+ public List<Capability> getCapabilities(String namespace)
+ {
+ if (m_cachedCapabilities == null)
+ {
+ List<Capability> caps = new ArrayList<Capability>();
+
+ // Wrap host capabilities.
+ for (Capability cap : m_host.getCapabilities(namespace))
+ {
+ caps.add(new WrappedCapability(this, cap));
+ }
+
+ // Wrap fragment capabilities.
+ if (m_fragments != null)
+ {
+ for (Resource fragment : m_fragments)
+ {
+ for (Capability cap : fragment.getCapabilities(namespace))
+ {
+ // Filter out identity capabilities, since they
+ // are not part of the fragment payload.
+ if (!cap.getNamespace()
+ .equals(IdentityNamespace.IDENTITY_NAMESPACE))
+ {
+ caps.add(new WrappedCapability(this, cap));
+ }
+ }
+ }
+ }
+ m_cachedCapabilities = Collections.unmodifiableList(caps);
+ }
+ return m_cachedCapabilities;
+ }
+
+ public List<Requirement> getRequirements(String namespace)
+ {
+ if (m_cachedRequirements == null)
+ {
+ List<Requirement> reqs = new ArrayList<Requirement>();
+
+ // Wrap host requirements.
+ for (Requirement req : m_host.getRequirements(null))
+ {
+ reqs.add(new WrappedRequirement(this, req));
+ }
+
+ // Wrap fragment requirements.
+ if (m_fragments != null)
+ {
+ for (Resource fragment : m_fragments)
+ {
+ for (Requirement req : fragment.getRequirements(null))
+ {
+ // Filter out host and execution environment requirements,
+ // since they are not part of the fragment payload.
+ if (!req.getNamespace().equals(HostNamespace.HOST_NAMESPACE)
+ && !req.getNamespace().equals(
+ ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE))
+ {
+ reqs.add(new WrappedRequirement(this, req));
+ }
+ }
+ }
+ }
+ m_cachedRequirements = Collections.unmodifiableList(reqs);
+ }
+ return m_cachedRequirements;
+ }
+
+ public String toString()
+ {
+ return m_host.toString();
+ }
+} \ No newline at end of file

Back to the top