You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ma...@apache.org on 2013/04/04 11:43:37 UTC
svn commit: r1464402 [8/11] - in /ace/trunk: org.apache.ace.deployment.api/
org.apache.ace.deployment.deploymentadmin/ org.apache.ace.deployment.itest/
org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/
org.apache.ace.deployment.provider...
Added: ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResolverImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResolverImpl.java?rev=1464402&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResolverImpl.java (added)
+++ ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResolverImpl.java Thu Apr 4 09:43:34 2013
@@ -0,0 +1,1781 @@
+/*
+ * 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.framework.resolver;
+
+import java.util.ArrayList;
+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.SortedSet;
+//import org.apache.felix.framework.BundleWiringImpl;
+import org.apache.felix.framework.Logger;
+import org.apache.felix.framework.capabilityset.CapabilitySet;
+import org.apache.felix.framework.util.FelixConstants;
+import org.apache.felix.framework.util.Util;
+import org.apache.felix.framework.wiring.BundleCapabilityImpl;
+import org.apache.felix.framework.wiring.BundleRequirementImpl;
+import org.osgi.framework.Constants;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWire;
+
+public class ResolverImpl implements Resolver
+{
+ private final Logger m_logger;
+
+ // Holds candidate permutations based on permutating "uses" chains.
+ // These permutations are given higher priority.
+ private final List<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>();
+
+ public ResolverImpl(Logger logger)
+ {
+ m_logger = logger;
+ }
+
+ public Map<BundleRevision, List<ResolverWire>> resolve(
+ ResolverState state,
+ Set<BundleRevision> mandatoryRevisions,
+ Set<BundleRevision> optionalRevisions,
+ Set<BundleRevision> ondemandFragments)
+ {
+ Map<BundleRevision, List<ResolverWire>> wireMap =
+ new HashMap<BundleRevision, List<ResolverWire>>();
+ Map<BundleRevision, Packages> revisionPkgMap =
+ new HashMap<BundleRevision, Packages>();
+
+ boolean retry;
+ do
+ {
+ retry = false;
+
+ try
+ {
+ // Create object to hold all candidates.
+ Candidates allCandidates = new Candidates();
+
+ // Populate mandatory revisions; since these are mandatory
+ // revisions, failure throws a resolve exception.
+ for (Iterator<BundleRevision> it = mandatoryRevisions.iterator();
+ it.hasNext(); )
+ {
+ BundleRevision br = it.next();
+ if (Util.isFragment(br) || (br.getWiring() == null))
+ {
+ allCandidates.populate(state, br, Candidates.MANDATORY);
+ }
+ else
+ {
+ it.remove();
+ }
+ }
+
+ // Populate optional revisions; since these are optional
+ // revisions, failure does not throw a resolve exception.
+ for (BundleRevision br : optionalRevisions)
+ {
+ boolean isFragment = Util.isFragment(br);
+ if (isFragment || (br.getWiring() == null))
+ {
+ allCandidates.populate(state, br, Candidates.OPTIONAL);
+ }
+ }
+
+ // Populate ondemand fragments; since these are optional
+ // revisions, failure does not throw a resolve exception.
+ for (BundleRevision br : ondemandFragments)
+ {
+ boolean isFragment = Util.isFragment(br);
+ if (isFragment)
+ {
+ allCandidates.populate(state, br, Candidates.ON_DEMAND);
+ }
+ }
+
+ // Merge any fragments into hosts.
+ allCandidates.prepare(getResolvedSingletons(state));
+
+ // Create a combined list of populated revisions; for
+ // optional revisions. We do not need to consider ondemand
+ // fragments, since they will only be pulled in if their
+ // host is already present.
+ Set<BundleRevision> allRevisions =
+ new HashSet<BundleRevision>(mandatoryRevisions);
+ for (BundleRevision br : optionalRevisions)
+ {
+ if (allCandidates.isPopulated(br))
+ {
+ allRevisions.add(br);
+ }
+ }
+
+ // Record the initial candidate permutation.
+ m_usesPermutations.add(allCandidates);
+
+ ResolveException rethrow = null;
+
+ // If a populated revision is a fragment, then its host
+ // must ultimately be verified, so store its host requirement
+ // to use for package space calculation.
+ Map<BundleRevision, List<BundleRequirement>> hostReqs =
+ new HashMap<BundleRevision, List<BundleRequirement>>();
+ for (BundleRevision br : allRevisions)
+ {
+ if (Util.isFragment(br))
+ {
+ hostReqs.put(
+ br,
+ br.getDeclaredRequirements(BundleRevision.HOST_NAMESPACE));
+ }
+ }
+
+ do
+ {
+ rethrow = null;
+
+ revisionPkgMap.clear();
+ m_packageSourcesCache.clear();
+
+ allCandidates = (m_usesPermutations.size() > 0)
+ ? m_usesPermutations.remove(0)
+ : m_importPermutations.remove(0);
+//allCandidates.dump();
+
+ for (BundleRevision br : allRevisions)
+ {
+ BundleRevision target = br;
+
+ // If we are resolving a fragment, then get its
+ // host candidate and verify it instead.
+ List<BundleRequirement> hostReq = hostReqs.get(br);
+ if (hostReq != null)
+ {
+ target = allCandidates.getCandidates(hostReq.get(0))
+ .iterator().next().getRevision();
+ }
+
+ calculatePackageSpaces(
+ allCandidates.getWrappedHost(target), allCandidates, revisionPkgMap,
+ new HashMap(), new HashSet());
+//System.out.println("+++ PACKAGE SPACES START +++");
+//dumpRevisionPkgMap(revisionPkgMap);
+//System.out.println("+++ PACKAGE SPACES END +++");
+
+ try
+ {
+ checkPackageSpaceConsistency(
+ false, allCandidates.getWrappedHost(target),
+ allCandidates, revisionPkgMap, new HashMap());
+ }
+ catch (ResolveException ex)
+ {
+ rethrow = ex;
+ }
+ }
+ }
+ while ((rethrow != null)
+ && ((m_usesPermutations.size() > 0) || (m_importPermutations.size() > 0)));
+
+ // If there is a resolve exception, then determine if an
+ // optionally resolved revision 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)
+ {
+ BundleRevision faultyRevision =
+ getActualBundleRevision(rethrow.getRevision());
+ if (rethrow.getRequirement() instanceof HostedRequirement)
+ {
+ faultyRevision =
+ ((HostedRequirement) rethrow.getRequirement())
+ .getOriginalRequirement().getRevision();
+ }
+ if (optionalRevisions.remove(faultyRevision))
+ {
+ retry = true;
+ }
+ else if (ondemandFragments.remove(faultyRevision))
+ {
+ retry = true;
+ }
+ else
+ {
+ throw rethrow;
+ }
+ }
+ // If there is no exception to rethrow, then this was a clean
+ // resolve, so populate the wire map.
+ else
+ {
+ for (BundleRevision br : allRevisions)
+ {
+ BundleRevision target = br;
+
+ // If we are resolving a fragment, then we
+ // actually want to populate its host's wires.
+ List<BundleRequirement> hostReq = hostReqs.get(br);
+ if (hostReq != null)
+ {
+ target = allCandidates.getCandidates(hostReq.get(0))
+ .iterator().next().getRevision();
+ }
+
+ if (allCandidates.isPopulated(target))
+ {
+ wireMap =
+ populateWireMap(
+ allCandidates.getWrappedHost(target),
+ revisionPkgMap, wireMap, allCandidates);
+ }
+ }
+ }
+ }
+ finally
+ {
+ // Always clear the state.
+ m_usesPermutations.clear();
+ m_importPermutations.clear();
+ }
+ }
+ while (retry);
+
+ return wireMap;
+ }
+
+ public Map<BundleRevision, List<ResolverWire>> resolve(
+ ResolverState state, BundleRevision revision, String pkgName,
+ Set<BundleRevision> ondemandFragments)
+ {
+ // We can only create a dynamic import if the following
+ // conditions are met:
+ // 1. The specified revision 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 revision.
+ // 5. The package in question matches a dynamic import of the revision.
+ // The following call checks all of these conditions and returns
+ // the associated dynamic import and matching capabilities.
+ Candidates allCandidates =
+ getDynamicImportCandidates(state, revision, pkgName);
+ if (allCandidates != null)
+ {
+ Map<BundleRevision, List<ResolverWire>> wireMap = new HashMap<BundleRevision, List<ResolverWire>>();
+ Map<BundleRevision, Packages> revisionPkgMap = new HashMap<BundleRevision, Packages>();
+
+ boolean retry;
+ do
+ {
+ retry = false;
+
+ try
+ {
+ // Try to populate optional fragments.
+ for (BundleRevision br : ondemandFragments)
+ {
+ if (Util.isFragment(br))
+ {
+ allCandidates.populate(state, br, Candidates.ON_DEMAND);
+ }
+ }
+
+ // Merge any fragments into hosts.
+ allCandidates.prepare(getResolvedSingletons(state));
+
+ // Record the initial candidate permutation.
+ m_usesPermutations.add(allCandidates);
+
+ ResolveException rethrow = null;
+
+ do
+ {
+ rethrow = null;
+
+ revisionPkgMap.clear();
+ m_packageSourcesCache.clear();
+
+ allCandidates = (m_usesPermutations.size() > 0)
+ ? m_usesPermutations.remove(0)
+ : m_importPermutations.remove(0);
+//allCandidates.dump();
+
+ // For a dynamic import, the instigating revision
+ // will never be a fragment since fragments never
+ // execute code, so we don't need to check for
+ // this case like we do for a normal resolve.
+
+ calculatePackageSpaces(
+ allCandidates.getWrappedHost(revision), allCandidates, revisionPkgMap,
+ new HashMap(), new HashSet());
+//System.out.println("+++ PACKAGE SPACES START +++");
+//dumpRevisionPkgMap(revisionPkgMap);
+//System.out.println("+++ PACKAGE SPACES END +++");
+
+ try
+ {
+ checkPackageSpaceConsistency(
+ false, allCandidates.getWrappedHost(revision),
+ allCandidates, revisionPkgMap, new HashMap());
+ }
+ catch (ResolveException ex)
+ {
+ rethrow = ex;
+ }
+ }
+ while ((rethrow != null)
+ && ((m_usesPermutations.size() > 0) || (m_importPermutations.size() > 0)));
+
+ // If there is a resolve exception, then determine if an
+ // optionally resolved revision is to blame (typically a fragment).
+ // If so, then remove the optionally resolved revision and try
+ // again; otherwise, rethrow the resolve exception.
+ if (rethrow != null)
+ {
+ BundleRevision faultyRevision =
+ getActualBundleRevision(rethrow.getRevision());
+ if (rethrow.getRequirement() instanceof HostedRequirement)
+ {
+ faultyRevision =
+ ((HostedRequirement) rethrow.getRequirement())
+ .getOriginalRequirement().getRevision();
+ }
+ if (ondemandFragments.remove(faultyRevision))
+ {
+ retry = true;
+ }
+ else
+ {
+ throw rethrow;
+ }
+ }
+ // If there is no exception to rethrow, then this was a clean
+ // resolve, so populate the wire map.
+ else
+ {
+ wireMap = populateDynamicWireMap(
+ revision, pkgName, revisionPkgMap, wireMap, allCandidates);
+ return wireMap;
+ }
+ }
+ finally
+ {
+ // Always clear the state.
+ m_usesPermutations.clear();
+ m_importPermutations.clear();
+ }
+ }
+ while (retry);
+ }
+
+ return null;
+ }
+
+ private static List<BundleRevision> getResolvedSingletons(ResolverState state)
+ {
+ BundleRequirementImpl req = new BundleRequirementImpl(
+ null,
+ BundleCapabilityImpl.SINGLETON_NAMESPACE,
+ Collections.EMPTY_MAP,
+ Collections.EMPTY_MAP);
+ SortedSet<BundleCapability> caps = state.getCandidates(req, true);
+ List<BundleRevision> singletons = new ArrayList();
+ for (BundleCapability cap : caps)
+ {
+ if (cap.getRevision().getWiring() != null)
+ {
+ singletons.add(cap.getRevision());
+ }
+ }
+ return singletons;
+ }
+
+ private static Candidates getDynamicImportCandidates(
+ ResolverState state, BundleRevision revision, String pkgName)
+ {
+ // Unresolved revisions cannot dynamically import, nor can the default
+ // package be dynamically imported.
+ if ((revision.getWiring() == null) || pkgName.length() == 0)
+ {
+ return null;
+ }
+
+ // If the revision doesn't have dynamic imports, then just return
+ // immediately.
+ List<BundleRequirement> dynamics =
+ Util.getDynamicRequirements(revision.getWiring().getRequirements(null));
+ if ((dynamics == null) || dynamics.isEmpty())
+ {
+ return null;
+ }
+
+ // If the revision exports this package, then we cannot
+ // attempt to dynamically import it.
+ for (BundleCapability cap : revision.getWiring().getCapabilities(null))
+ {
+ if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
+ && cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE).equals(pkgName))
+ {
+ return null;
+ }
+ }
+
+ // If this revision already imports or requires this package, then
+ // we cannot dynamically import it.
+ /* TODO: karl - is this correct?
+ * if (((BundleWiringImpl) revision.getWiring()).hasPackageSource(pkgName))
+ * {
+ * return null;
+ * }
+ */
+
+ // Determine if any providers of the package exist.
+ Map<String, Object> attrs = Collections.singletonMap(
+ BundleRevision.PACKAGE_NAMESPACE, (Object) pkgName);
+ BundleRequirementImpl req = new BundleRequirementImpl(
+ revision,
+ BundleRevision.PACKAGE_NAMESPACE,
+ Collections.EMPTY_MAP,
+ attrs);
+ SortedSet<BundleCapability> candidates = state.getCandidates(req, false);
+
+ // Try to find a dynamic requirement that matches the capabilities.
+ BundleRequirementImpl dynReq = null;
+ for (int dynIdx = 0;
+ (candidates.size() > 0) && (dynReq == null) && (dynIdx < dynamics.size());
+ dynIdx++)
+ {
+ for (Iterator<BundleCapability> itCand = candidates.iterator();
+ (dynReq == null) && itCand.hasNext(); )
+ {
+ BundleCapability cap = itCand.next();
+ if (CapabilitySet.matches(
+ (BundleCapabilityImpl) cap,
+ ((BundleRequirementImpl) dynamics.get(dynIdx)).getFilter()))
+ {
+ dynReq = (BundleRequirementImpl) dynamics.get(dynIdx);
+ }
+ }
+ }
+
+ // If we found a matching dynamic requirement, then filter out
+ // any candidates that do not match it.
+ if (dynReq != null)
+ {
+ for (Iterator<BundleCapability> itCand = candidates.iterator();
+ itCand.hasNext(); )
+ {
+ BundleCapability cap = itCand.next();
+ if (!CapabilitySet.matches(
+ (BundleCapabilityImpl) cap, dynReq.getFilter()))
+ {
+ itCand.remove();
+ }
+ }
+ }
+ else
+ {
+ candidates.clear();
+ }
+
+ Candidates allCandidates = null;
+
+ if (candidates.size() > 0)
+ {
+ allCandidates = new Candidates();
+ allCandidates.populateDynamic(state, revision, dynReq, candidates);
+ }
+
+ return allCandidates;
+ }
+
+ private void calculatePackageSpaces(
+ BundleRevision revision,
+ Candidates allCandidates,
+ Map<BundleRevision, Packages> revisionPkgMap,
+ Map<BundleCapability, List<BundleRevision>> usesCycleMap,
+ Set<BundleRevision> cycle)
+ {
+ if (cycle.contains(revision))
+ {
+ return;
+ }
+ cycle.add(revision);
+
+ // Create parallel arrays for requirement and proposed candidate
+ // capability or actual capability if revision is resolved or not.
+ List<BundleRequirement> reqs = new ArrayList();
+ List<BundleCapability> caps = new ArrayList();
+ boolean isDynamicImporting = false;
+ if (revision.getWiring() != null)
+ {
+ // Use wires to get actual requirements and satisfying capabilities.
+ for (BundleWire wire : revision.getWiring().getRequiredWires(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.
+ BundleRequirement r = wire.getRequirement();
+ if (!r.getRevision().equals(wire.getRequirerWiring().getRevision())
+ || ((r.getDirectives().get(Constants.RESOLUTION_DIRECTIVE) != null)
+ && r.getDirectives().get(Constants.RESOLUTION_DIRECTIVE).equals("dynamic")))
+ {
+ r = new HostedRequirement(
+ wire.getRequirerWiring().getRevision(),
+ (BundleRequirementImpl) r);
+ }
+ // Wrap the capability as a hosted capability if it comes
+ // from a fragment, since we will need to know the host.
+ BundleCapability c = wire.getCapability();
+ if (!c.getRevision().equals(wire.getProviderWiring().getRevision()))
+ {
+ c = new HostedCapability(
+ wire.getProviderWiring().getRevision(),
+ (BundleCapabilityImpl) c);
+ }
+ reqs.add(r);
+ caps.add(c);
+ }
+
+ // Since the revision is resolved, it could be dynamically importing,
+ // so check to see if there are candidates for any of its dynamic
+ // imports.
+ for (BundleRequirement req
+ : Util.getDynamicRequirements(revision.getWiring().getRequirements(null)))
+ {
+ // Get the candidates for the current requirement.
+ SortedSet<BundleCapability> candCaps =
+ allCandidates.getCandidates((BundleRequirementImpl) req);
+ // Optional requirements may not have any candidates.
+ if (candCaps == null)
+ {
+ continue;
+ }
+
+ BundleCapability cap = candCaps.iterator().next();
+ 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 (BundleRequirement req : revision.getDeclaredRequirements(null))
+ {
+ String resolution = req.getDirectives().get(Constants.RESOLUTION_DIRECTIVE);
+ if ((resolution == null)
+ || !resolution.equals(FelixConstants.RESOLUTION_DYNAMIC))
+ {
+ // Get the candidates for the current requirement.
+ SortedSet<BundleCapability> candCaps =
+ allCandidates.getCandidates((BundleRequirementImpl) req);
+ // Optional requirements may not have any candidates.
+ if (candCaps == null)
+ {
+ continue;
+ }
+
+ BundleCapability cap = candCaps.iterator().next();
+ reqs.add(req);
+ caps.add(cap);
+ }
+ }
+ }
+
+ // First, add all exported packages to the target revision's package space.
+ calculateExportedPackages(revision, allCandidates, revisionPkgMap);
+ Packages revisionPkgs = revisionPkgMap.get(revision);
+
+ // Second, add all imported packages to the target revision's package space.
+ for (int i = 0; i < reqs.size(); i++)
+ {
+ BundleRequirement req = reqs.get(i);
+ BundleCapability cap = caps.get(i);
+ calculateExportedPackages(cap.getRevision(), allCandidates, revisionPkgMap);
+ mergeCandidatePackages(
+ revision, req, cap, revisionPkgMap, allCandidates,
+ new HashMap<BundleRevision, List<BundleCapability>>());
+ }
+
+ // Third, have all candidates to calculate their package spaces.
+ for (int i = 0; i < caps.size(); i++)
+ {
+ calculatePackageSpaces(
+ caps.get(i).getRevision(), allCandidates, revisionPkgMap,
+ usesCycleMap, cycle);
+ }
+
+ // Fourth, if the target revision 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 revisions because their
+ // package space is consistent by definition and these uses constraints
+ // are only needed to verify the consistency of a resolving revision. The
+ // only exception is if a resolved revision 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 ((revision.getWiring() == null) || isDynamicImporting)
+ {
+ // Merge uses constraints from required capabilities.
+ for (int i = 0; i < reqs.size(); i++)
+ {
+ BundleRequirement req = reqs.get(i);
+ BundleCapability cap = caps.get(i);
+ // Ignore bundle/package requirements, since they are
+ // considered below.
+ if (!req.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE)
+ && !req.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
+ {
+ List<BundleRequirement> blameReqs = new ArrayList();
+ blameReqs.add(req);
+
+ mergeUses(
+ revision,
+ revisionPkgs,
+ cap,
+ blameReqs,
+ revisionPkgMap,
+ allCandidates,
+ usesCycleMap);
+ }
+ }
+ // Merge uses constraints from imported packages.
+ for (Entry<String, List<Blame>> entry : revisionPkgs.m_importedPkgs.entrySet())
+ {
+ for (Blame blame : entry.getValue())
+ {
+ // Ignore revisions that import from themselves.
+ if (!blame.m_cap.getRevision().equals(revision))
+ {
+ List<BundleRequirement> blameReqs = new ArrayList();
+ blameReqs.add(blame.m_reqs.get(0));
+
+ mergeUses(
+ revision,
+ revisionPkgs,
+ blame.m_cap,
+ blameReqs,
+ revisionPkgMap,
+ allCandidates,
+ usesCycleMap);
+ }
+ }
+ }
+ // Merge uses constraints from required bundles.
+ for (Entry<String, List<Blame>> entry : revisionPkgs.m_requiredPkgs.entrySet())
+ {
+ for (Blame blame : entry.getValue())
+ {
+ List<BundleRequirement> blameReqs = new ArrayList();
+ blameReqs.add(blame.m_reqs.get(0));
+
+ mergeUses(
+ revision,
+ revisionPkgs,
+ blame.m_cap,
+ blameReqs,
+ revisionPkgMap,
+ allCandidates,
+ usesCycleMap);
+ }
+ }
+ }
+ }
+
+ private void mergeCandidatePackages(
+ BundleRevision current, BundleRequirement currentReq, BundleCapability candCap,
+ Map<BundleRevision, Packages> revisionPkgMap,
+ Candidates allCandidates, Map<BundleRevision, List<BundleCapability>> cycles)
+ {
+ List<BundleCapability> cycleCaps = cycles.get(current);
+ if (cycleCaps == null)
+ {
+ cycleCaps = new ArrayList<BundleCapability>();
+ cycles.put(current, cycleCaps);
+ }
+ if (cycleCaps.contains(candCap))
+ {
+ return;
+ }
+ cycleCaps.add(candCap);
+
+ if (candCap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
+ {
+ mergeCandidatePackage(
+ current, false, currentReq, candCap, revisionPkgMap);
+ }
+ else if (candCap.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE))
+ {
+// TODO: FELIX3 - THIS NEXT LINE IS A HACK. IMPROVE HOW/WHEN WE CALCULATE EXPORTS.
+ calculateExportedPackages(
+ candCap.getRevision(), allCandidates, revisionPkgMap);
+
+ // Get the candidate's package space to determine which packages
+ // will be visible to the current revision.
+ Packages candPkgs = revisionPkgMap.get(candCap.getRevision());
+
+ // We have to merge all exported packages from the candidate,
+ // since the current revision requires it.
+ for (Entry<String, Blame> entry : candPkgs.m_exportedPkgs.entrySet())
+ {
+ mergeCandidatePackage(
+ current,
+ true,
+ currentReq,
+ entry.getValue().m_cap,
+ revisionPkgMap);
+ }
+
+ // If the candidate requires any other bundles with reexport visibility,
+ // then we also need to merge their packages too.
+ if (candCap.getRevision().getWiring() != null)
+ {
+ for (BundleWire bw
+ : candCap.getRevision().getWiring().getRequiredWires(null))
+ {
+ if (bw.getRequirement().getNamespace()
+ .equals(BundleRevision.BUNDLE_NAMESPACE))
+ {
+ String value = bw.getRequirement()
+ .getDirectives().get(Constants.VISIBILITY_DIRECTIVE);
+ if ((value != null)
+ && value.equals(Constants.VISIBILITY_REEXPORT))
+ {
+ mergeCandidatePackages(
+ current,
+ currentReq,
+ bw.getCapability(),
+ revisionPkgMap,
+ allCandidates,
+ cycles);
+ }
+ }
+ }
+ }
+ else
+ {
+ List<BundleRequirement> reqs = (candCap.getRevision().getWiring() != null)
+ ? candCap.getRevision().getWiring().getRequirements(null)
+ : candCap.getRevision().getDeclaredRequirements(null);
+ for (BundleRequirement req
+ : candCap.getRevision().getDeclaredRequirements(null))
+ {
+ if (req.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE))
+ {
+ String value =
+ req.getDirectives().get(Constants.VISIBILITY_DIRECTIVE);
+ if ((value != null)
+ && value.equals(Constants.VISIBILITY_REEXPORT)
+ && (allCandidates.getCandidates(req) != null))
+ {
+ mergeCandidatePackages(
+ current,
+ currentReq,
+ allCandidates.getCandidates(req).iterator().next(),
+ revisionPkgMap,
+ allCandidates,
+ cycles);
+ }
+ }
+ }
+ }
+ }
+
+ cycles.remove(current);
+ }
+
+ private void mergeCandidatePackage(
+ BundleRevision current, boolean requires,
+ BundleRequirement currentReq, BundleCapability candCap,
+ Map<BundleRevision, Packages> revisionPkgMap)
+ {
+ if (candCap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
+ {
+ // Merge the candidate capability into the revision's package space
+ // for imported or required packages, appropriately.
+
+ String pkgName = (String)
+ candCap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE);
+
+ List blameReqs = new ArrayList();
+ blameReqs.add(currentReq);
+
+ Packages currentPkgs = revisionPkgMap.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));
+
+//dumpRevisionPkgs(current, currentPkgs);
+ }
+ }
+
+ private void mergeUses(
+ BundleRevision current, Packages currentPkgs,
+ BundleCapability mergeCap, List<BundleRequirement> blameReqs,
+ Map<BundleRevision, Packages> revisionPkgMap,
+ Candidates allCandidates,
+ Map<BundleCapability, List<BundleRevision>> cycleMap)
+ {
+ // If there are no uses, then just return.
+ // If the candidate revision is the same as the current revision,
+ // 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.getRevision()))
+ {
+ return;
+ }
+
+ // Check for cycles.
+ List<BundleRevision> list = cycleMap.get(mergeCap);
+ if ((list != null) && list.contains(current))
+ {
+ return;
+ }
+ list = (list == null) ? new ArrayList<BundleRevision>() : list;
+ list.add(current);
+ cycleMap.put(mergeCap, list);
+
+ for (BundleCapability candSourceCap : getPackageSources(mergeCap, revisionPkgMap))
+ {
+ for (String usedPkgName : ((BundleCapabilityImpl) candSourceCap).getUses())
+ {
+ Packages candSourcePkgs = revisionPkgMap.get(candSourceCap.getRevision());
+ List<Blame> candSourceBlames = null;
+ // Check to see if the used package is exported.
+ Blame candExportedBlame = candSourcePkgs.m_exportedPkgs.get(usedPkgName);
+ if (candExportedBlame != null)
+ {
+ candSourceBlames = new ArrayList(1);
+ candSourceBlames.add(candExportedBlame);
+ }
+ else
+ {
+ // If the used package is not exported, check to see if it
+ // is required.
+ candSourceBlames = candSourcePkgs.m_requiredPkgs.get(usedPkgName);
+ // Lastly, if the used package is not required, check to see if it
+ // is imported.
+ candSourceBlames = (candSourceBlames != null)
+ ? candSourceBlames : candSourcePkgs.m_importedPkgs.get(usedPkgName);
+ }
+
+ // If the used package cannot be found, then just ignore it
+ // since it has no impact.
+ if (candSourceBlames == null)
+ {
+ continue;
+ }
+
+ List<Blame> usedCaps = currentPkgs.m_usedPkgs.get(usedPkgName);
+ if (usedCaps == null)
+ {
+ usedCaps = new ArrayList<Blame>();
+ currentPkgs.m_usedPkgs.put(usedPkgName, usedCaps);
+ }
+ for (Blame blame : candSourceBlames)
+ {
+ if (blame.m_reqs != null)
+ {
+ List<BundleRequirement> blameReqs2 = new ArrayList(blameReqs);
+ blameReqs2.add(blame.m_reqs.get(blame.m_reqs.size() - 1));
+ usedCaps.add(new Blame(blame.m_cap, blameReqs2));
+ mergeUses(current, currentPkgs, blame.m_cap, blameReqs2,
+ revisionPkgMap, allCandidates, cycleMap);
+ }
+ else
+ {
+ usedCaps.add(new Blame(blame.m_cap, blameReqs));
+ mergeUses(current, currentPkgs, blame.m_cap, blameReqs,
+ revisionPkgMap, allCandidates, cycleMap);
+ }
+ }
+ }
+ }
+ }
+
+ private void checkPackageSpaceConsistency(
+ boolean isDynamicImporting,
+ BundleRevision revision,
+ Candidates allCandidates,
+ Map<BundleRevision, Packages> revisionPkgMap,
+ Map<BundleRevision, Object> resultCache)
+ {
+ if ((revision.getWiring() != null) && !isDynamicImporting)
+ {
+ return;
+ }
+ else if(resultCache.containsKey(revision))
+ {
+ return;
+ }
+
+ Packages pkgs = revisionPkgMap.get(revision);
+
+ ResolveException rethrow = null;
+ Candidates permutation = null;
+ Set<BundleRequirement> mutated = null;
+
+ // Check for conflicting imports from fragments.
+ 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.getRevision().equals(blame.m_cap.getRevision()))
+ {
+ // Try to permutate the conflicting requirement.
+ permutate(allCandidates, blame.m_reqs.get(0), m_importPermutations);
+ // Try to permutate the source requirement.
+ permutate(allCandidates, sourceBlame.m_reqs.get(0), m_importPermutations);
+ // Report conflict.
+ ResolveException ex = new ResolveException(
+ "Uses constraint violation. Unable to resolve bundle revision "
+ + revision.getSymbolicName()
+ + " [" + revision
+ + "] because it is exposed to package '"
+ + entry.getKey()
+ + "' from bundle revisions "
+ + sourceBlame.m_cap.getRevision().getSymbolicName()
+ + " [" + sourceBlame.m_cap.getRevision()
+ + "] and "
+ + blame.m_cap.getRevision().getSymbolicName()
+ + " [" + blame.m_cap.getRevision()
+ + "] via two dependency chains.\n\nChain 1:\n"
+ + toStringBlame(sourceBlame)
+ + "\n\nChain 2:\n"
+ + toStringBlame(blame),
+ revision,
+ 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;
+ }
+ }
+ }
+ }
+
+ 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 (Blame usedBlame : pkgs.m_usedPkgs.get(pkgName))
+ {
+ if (!isCompatible(exportBlame.m_cap, usedBlame.m_cap, revisionPkgMap))
+ {
+ // 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 ResolveException(
+ "Uses constraint violation. Unable to resolve bundle revision "
+ + revision.getSymbolicName()
+ + " [" + revision
+ + "] because it exports package '"
+ + pkgName
+ + "' and is also exposed to it from bundle revision "
+ + usedBlame.m_cap.getRevision().getSymbolicName()
+ + " [" + usedBlame.m_cap.getRevision()
+ + "] via the following dependency chain:\n\n"
+ + toStringBlame(usedBlame),
+ null,
+ null);
+
+ mutated = (mutated != null)
+ ? mutated
+ : new HashSet<BundleRequirement>();
+
+ for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--)
+ {
+ BundleRequirement req = usedBlame.m_reqs.get(reqIdx);
+
+ // If we've already permutated this requirement in another
+ // uses constraint, don't permutate it again just continue
+ // with the next uses constraint.
+ if (mutated.contains(req))
+ {
+ break;
+ }
+
+ // See if we can permutate the candidates for blamed
+ // requirement; there may be no candidates if the revision
+ // associated with the requirement is already resolved.
+ SortedSet<BundleCapability> candidates =
+ permutation.getCandidates(req);
+ if ((candidates != null) && (candidates.size() > 1))
+ {
+ mutated.add(req);
+ Iterator it = candidates.iterator();
+ it.next();
+ it.remove();
+ // Continue with the next uses constraint.
+ break;
+ }
+ }
+ }
+ }
+
+ if (rethrow != null)
+ {
+ if (mutated.size() > 0)
+ {
+ m_usesPermutations.add(permutation);
+ }
+ m_logger.log(
+ Logger.LOG_DEBUG,
+ "Candidate permutation failed due to a conflict between "
+ + "an export and import; will try another if possible.",
+ rethrow);
+ throw rethrow;
+ }
+ }
+
+ // Check if there are any uses conflicts with imported packages.
+ for (Entry<String, List<Blame>> entry : pkgs.m_importedPkgs.entrySet())
+ {
+ for (Blame importBlame : entry.getValue())
+ {
+ String pkgName = entry.getKey();
+ if (!pkgs.m_usedPkgs.containsKey(pkgName))
+ {
+ continue;
+ }
+ for (Blame usedBlame : pkgs.m_usedPkgs.get(pkgName))
+ {
+ if (!isCompatible(importBlame.m_cap, usedBlame.m_cap, revisionPkgMap))
+ {
+ // Create a candidate permutation that eliminates any candidates
+ // that conflict with existing selected candidates.
+ permutation = (permutation != null)
+ ? permutation
+ : allCandidates.copy();
+ rethrow = (rethrow != null)
+ ? rethrow
+ : new ResolveException(
+ "Uses constraint violation. Unable to resolve bundle revision "
+ + revision.getSymbolicName()
+ + " [" + revision
+ + "] because it is exposed to package '"
+ + pkgName
+ + "' from bundle revisions "
+ + importBlame.m_cap.getRevision().getSymbolicName()
+ + " [" + importBlame.m_cap.getRevision()
+ + "] and "
+ + usedBlame.m_cap.getRevision().getSymbolicName()
+ + " [" + usedBlame.m_cap.getRevision()
+ + "] via two dependency chains.\n\nChain 1:\n"
+ + toStringBlame(importBlame)
+ + "\n\nChain 2:\n"
+ + toStringBlame(usedBlame),
+ null,
+ null);
+
+ mutated = (mutated != null)
+ ? mutated
+ : new HashSet();
+
+ for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--)
+ {
+ BundleRequirement req = usedBlame.m_reqs.get(reqIdx);
+
+ // If we've already permutated this requirement in another
+ // uses constraint, don't permutate it again just continue
+ // with the next uses constraint.
+ if (mutated.contains(req))
+ {
+ break;
+ }
+
+ // See if we can permutate the candidates for blamed
+ // requirement; there may be no candidates if the revision
+ // associated with the requirement is already resolved.
+ SortedSet<BundleCapability> candidates =
+ permutation.getCandidates(req);
+ if ((candidates != null) && (candidates.size() > 1))
+ {
+ mutated.add(req);
+ Iterator it = candidates.iterator();
+ it.next();
+ it.remove();
+ // Continue with the next uses constraint.
+ break;
+ }
+ }
+ }
+ }
+
+ // If there was a uses conflict, then we should add a uses
+ // permutation if we were able to permutate any candidates.
+ // Additionally, we should try to push an import permutation
+ // for the original import to force a backtracking on the
+ // original candidate decision if no viable candidate is found
+ // for the conflicting uses constraint.
+ if (rethrow != null)
+ {
+ // Add uses permutation if we mutated any candidates.
+ if (mutated.size() > 0)
+ {
+ m_usesPermutations.add(permutation);
+ }
+
+ // Try to permutate the candidate for the original
+ // import requirement; only permutate it if we haven't
+ // done so already.
+ BundleRequirement req = importBlame.m_reqs.get(0);
+ if (!mutated.contains(req))
+ {
+ // Since there may be lots of uses constraint violations
+ // with existing import decisions, we may end up trying
+ // to permutate the same import a lot of times, so we should
+ // try to check if that the case and only permutate it once.
+ permutateIfNeeded(allCandidates, req, m_importPermutations);
+ }
+
+ m_logger.log(
+ Logger.LOG_DEBUG,
+ "Candidate permutation failed due to a conflict between "
+ + "imports; will try another if possible.",
+ rethrow);
+ throw rethrow;
+ }
+ }
+ }
+
+ resultCache.put(revision, Boolean.TRUE);
+
+ // Now check the consistency of all revisions on which the
+ // current revision depends. Keep track of the current number
+ // of permutations so we know if the lower level check was
+ // able to create a permutation or not in the case of failure.
+ int permCount = m_usesPermutations.size() + m_importPermutations.size();
+ for (Entry<String, List<Blame>> entry : pkgs.m_importedPkgs.entrySet())
+ {
+ for (Blame importBlame : entry.getValue())
+ {
+ if (!revision.equals(importBlame.m_cap.getRevision()))
+ {
+ try
+ {
+ checkPackageSpaceConsistency(
+ false, importBlame.m_cap.getRevision(),
+ allCandidates, revisionPkgMap, resultCache);
+ }
+ catch (ResolveException 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 revision
+ // to backtrack on our current candidate selection.
+ if (permCount == (m_usesPermutations.size() + m_importPermutations.size()))
+ {
+ BundleRequirement req = importBlame.m_reqs.get(0);
+ permutate(allCandidates, req, m_importPermutations);
+ }
+ throw ex;
+ }
+ }
+ }
+ }
+ }
+
+ private static void permutate(
+ Candidates allCandidates, BundleRequirement req, List<Candidates> permutations)
+ {
+ SortedSet<BundleCapability> candidates = allCandidates.getCandidates(req);
+ if (candidates.size() > 1)
+ {
+ Candidates perm = allCandidates.copy();
+ candidates = perm.getCandidates(req);
+ Iterator it = candidates.iterator();
+ it.next();
+ it.remove();
+ permutations.add(perm);
+ }
+ }
+
+ private static void permutateIfNeeded(
+ Candidates allCandidates, BundleRequirement req, List<Candidates> permutations)
+ {
+ SortedSet<BundleCapability> candidates = allCandidates.getCandidates(req);
+ if (candidates.size() > 1)
+ {
+ // Check existing permutations to make sure we haven't
+ // already permutated this requirement. This check for
+ // duplicate permutations is simplistic. It assumes if
+ // there is any permutation that contains a different
+ // initial candidate for the requirement in question,
+ // then it has already been permutated.
+ boolean permutated = false;
+ for (Candidates existingPerm : permutations)
+ {
+ Set<BundleCapability> existingPermCands = existingPerm.getCandidates(req);
+ if (!existingPermCands.iterator().next().equals(candidates.iterator().next()))
+ {
+ permutated = true;
+ }
+ }
+ // If we haven't already permutated the existing
+ // import, do so now.
+ if (!permutated)
+ {
+ permutate(allCandidates, req, permutations);
+ }
+ }
+ }
+
+ private static void calculateExportedPackages(
+ BundleRevision revision,
+ Candidates allCandidates,
+ Map<BundleRevision, Packages> revisionPkgMap)
+ {
+ Packages packages = revisionPkgMap.get(revision);
+ if (packages != null)
+ {
+ return;
+ }
+ packages = new Packages(revision);
+
+ // Get all exported packages.
+ List<BundleCapability> caps = (revision.getWiring() != null)
+ ? revision.getWiring().getCapabilities(null)
+ : revision.getDeclaredCapabilities(null);
+ Map<String, BundleCapability> exports =
+ new HashMap<String, BundleCapability>(caps.size());
+ for (BundleCapability cap : caps)
+ {
+ if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
+ {
+ if (!cap.getRevision().equals(revision))
+ {
+ cap = new HostedCapability(revision, (BundleCapabilityImpl) cap);
+ }
+ exports.put(
+ (String) cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE),
+ cap);
+ }
+ }
+ // Remove substitutable exports that were imported.
+ // For resolved revisions BundleWiring.getCapabilities()
+ // already excludes imported substitutable exports, but
+ // for resolving revisions we must look in the candidate
+ // map to determine which exports are substitutable.
+ if (!exports.isEmpty())
+ {
+ if (revision.getWiring() == null)
+ {
+ for (BundleRequirement req : revision.getDeclaredRequirements(null))
+ {
+ if (req.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
+ {
+ Set<BundleCapability> cands =
+ allCandidates.getCandidates((BundleRequirementImpl) req);
+ if ((cands != null) && !cands.isEmpty())
+ {
+ String pkgName = (String) cands.iterator().next()
+ .getAttributes().get(BundleRevision.PACKAGE_NAMESPACE);
+ exports.remove(pkgName);
+ }
+ }
+ }
+ }
+
+ // Add all non-substituted exports to the revisions's package space.
+ for (Entry<String, BundleCapability> entry : exports.entrySet())
+ {
+ packages.m_exportedPkgs.put(
+ entry.getKey(), new Blame(entry.getValue(), null));
+ }
+ }
+
+ revisionPkgMap.put(revision, packages);
+ }
+
+ private boolean isCompatible(
+ BundleCapability currentCap, BundleCapability candCap,
+ Map<BundleRevision, Packages> revisionPkgMap)
+ {
+ if ((currentCap != null) && (candCap != null))
+ {
+ if (currentCap.equals(candCap))
+ {
+ return true;
+ }
+
+ List<BundleCapability> currentSources =
+ getPackageSources(
+ currentCap,
+ revisionPkgMap);
+ List<BundleCapability> candSources =
+ getPackageSources(
+ candCap,
+ revisionPkgMap);
+
+ return currentSources.containsAll(candSources)
+ || candSources.containsAll(currentSources);
+ }
+ return true;
+ }
+
+ private Map<BundleCapability, List<BundleCapability>> m_packageSourcesCache
+ = new HashMap();
+
+ private List<BundleCapability> getPackageSources(
+ BundleCapability cap, Map<BundleRevision, Packages> revisionPkgMap)
+ {
+ if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
+ {
+ List<BundleCapability> sources = m_packageSourcesCache.get(cap);
+ if (sources == null)
+ {
+ sources = getPackageSourcesInternal(
+ cap, revisionPkgMap, new ArrayList(), new HashSet());
+ m_packageSourcesCache.put(cap, sources);
+ }
+ return sources;
+ }
+
+ if (!((BundleCapabilityImpl) cap).getUses().isEmpty())
+ {
+ List<BundleCapability> caps = new ArrayList<BundleCapability>(1);
+ caps.add(cap);
+ return caps;
+ }
+
+ return Collections.EMPTY_LIST;
+ }
+
+ private static List<BundleCapability> getPackageSourcesInternal(
+ BundleCapability cap, Map<BundleRevision, Packages> revisionPkgMap,
+ List<BundleCapability> sources, Set<BundleCapability> cycleMap)
+ {
+ if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
+ {
+ if (cycleMap.contains(cap))
+ {
+ return sources;
+ }
+ cycleMap.add(cap);
+
+ // Get the package name associated with the capability.
+ String pkgName = cap.getAttributes()
+ .get(BundleRevision.PACKAGE_NAMESPACE).toString();
+
+ // Since a revision can export the same package more than once, get
+ // all package capabilities for the specified package name.
+ List<BundleCapability> caps = (cap.getRevision().getWiring() != null)
+ ? cap.getRevision().getWiring().getCapabilities(null)
+ : cap.getRevision().getDeclaredCapabilities(null);
+ for (int capIdx = 0; capIdx < caps.size(); capIdx++)
+ {
+ if (caps.get(capIdx).getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
+ && caps.get(capIdx).getAttributes().get(BundleRevision.PACKAGE_NAMESPACE).equals(pkgName))
+ {
+ sources.add(caps.get(capIdx));
+ }
+ }
+
+ // Then get any addition sources for the package from required bundles.
+ Packages pkgs = revisionPkgMap.get(cap.getRevision());
+ List<Blame> required = pkgs.m_requiredPkgs.get(pkgName);
+ if (required != null)
+ {
+ for (Blame blame : required)
+ {
+ getPackageSourcesInternal(blame.m_cap, revisionPkgMap, sources, cycleMap);
+ }
+ }
+ }
+
+ return sources;
+ }
+
+ private static BundleRevision getActualBundleRevision(BundleRevision br)
+ {
+ if (br instanceof HostBundleRevision)
+ {
+ return ((HostBundleRevision) br).getHost();
+ }
+ return br;
+ }
+
+ private static BundleCapability getActualCapability(BundleCapability c)
+ {
+ if (c instanceof HostedCapability)
+ {
+ return ((HostedCapability) c).getOriginalCapability();
+ }
+ return c;
+ }
+
+ private static BundleRequirement getActualRequirement(BundleRequirement r)
+ {
+ if (r instanceof HostedRequirement)
+ {
+ return ((HostedRequirement) r).getOriginalRequirement();
+ }
+ return r;
+ }
+
+ private static Map<BundleRevision, List<ResolverWire>> populateWireMap(
+ BundleRevision revision, Map<BundleRevision, Packages> revisionPkgMap,
+ Map<BundleRevision, List<ResolverWire>> wireMap,
+ Candidates allCandidates)
+ {
+ BundleRevision unwrappedRevision = getActualBundleRevision(revision);
+ if ((unwrappedRevision.getWiring() == null)
+ && !wireMap.containsKey(unwrappedRevision))
+ {
+ wireMap.put(unwrappedRevision, (List<ResolverWire>) Collections.EMPTY_LIST);
+
+ List<ResolverWire> packageWires = new ArrayList<ResolverWire>();
+ List<ResolverWire> bundleWires = new ArrayList<ResolverWire>();
+ List<ResolverWire> capabilityWires = new ArrayList<ResolverWire>();
+
+ for (BundleRequirement req : revision.getDeclaredRequirements(null))
+ {
+ SortedSet<BundleCapability> cands = allCandidates.getCandidates(req);
+ if ((cands != null) && (cands.size() > 0))
+ {
+ BundleCapability cand = cands.iterator().next();
+ // Ignore revisions that import themselves.
+ if (!revision.equals(cand.getRevision()))
+ {
+ if (cand.getRevision().getWiring() == null)
+ {
+ populateWireMap(cand.getRevision(),
+ revisionPkgMap, wireMap, allCandidates);
+ }
+ Packages candPkgs = revisionPkgMap.get(cand.getRevision());
+ ResolverWire wire = new ResolverWireImpl(
+ unwrappedRevision,
+ getActualRequirement(req),
+ getActualBundleRevision(cand.getRevision()),
+ getActualCapability(cand));
+ if (req.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
+ {
+ packageWires.add(wire);
+ }
+ else if (req.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE))
+ {
+ bundleWires.add(wire);
+ }
+ else
+ {
+ capabilityWires.add(wire);
+ }
+ }
+ }
+ }
+
+ // Combine package wires with require wires last.
+ packageWires.addAll(bundleWires);
+ packageWires.addAll(capabilityWires);
+ wireMap.put(unwrappedRevision, packageWires);
+
+ // Add host wire for any fragments.
+ if (revision instanceof HostBundleRevision)
+ {
+ List<BundleRevision> fragments = ((HostBundleRevision) revision).getFragments();
+ for (BundleRevision fragment : fragments)
+ {
+ List<ResolverWire> hostWires = wireMap.get(fragment);
+ if (hostWires == null)
+ {
+ hostWires = new ArrayList<ResolverWire>();
+ wireMap.put(fragment, hostWires);
+ }
+ hostWires.add(
+ new ResolverWireImpl(
+ getActualBundleRevision(fragment),
+ fragment.getDeclaredRequirements(
+ BundleRevision.HOST_NAMESPACE).get(0),
+ unwrappedRevision,
+ unwrappedRevision.getDeclaredCapabilities(
+ BundleRevision.HOST_NAMESPACE).get(0)));
+ }
+ }
+ }
+
+ return wireMap;
+ }
+
+ private static Map<BundleRevision, List<ResolverWire>> populateDynamicWireMap(
+ BundleRevision revision, String pkgName, Map<BundleRevision, Packages> revisionPkgMap,
+ Map<BundleRevision, List<ResolverWire>> wireMap, Candidates allCandidates)
+ {
+ wireMap.put(revision, (List<ResolverWire>) Collections.EMPTY_LIST);
+
+ List<ResolverWire> packageWires = new ArrayList<ResolverWire>();
+
+ BundleRequirement dynReq = null;
+ BundleCapability dynCand = null;
+ for (BundleRequirement req
+ : Util.getDynamicRequirements(revision.getWiring().getRequirements(null)))
+ {
+ // Get the candidates for the current dynamic requirement.
+ SortedSet<BundleCapability> candCaps =
+ allCandidates.getCandidates((BundleRequirementImpl) req);
+ // Optional requirements may not have any candidates.
+ if ((candCaps == null) || candCaps.isEmpty())
+ {
+ continue;
+ }
+
+ // Record the dynamic requirement.
+ dynReq = req;
+ dynCand = candCaps.first();
+
+ // Can only dynamically import one at a time, so break
+ // out of the loop after the first.
+ break;
+ }
+
+ if (dynReq != null)
+ {
+ if (dynCand.getRevision().getWiring() == null)
+ {
+ populateWireMap(dynCand.getRevision(), revisionPkgMap, wireMap,
+ allCandidates);
+ }
+
+ Map<String, Object> attrs = new HashMap(1);
+ attrs.put(BundleRevision.PACKAGE_NAMESPACE, pkgName);
+ packageWires.add(
+ new ResolverWireImpl(
+ revision,
+ dynReq,
+ getActualBundleRevision(dynCand.getRevision()),
+ getActualCapability(dynCand)));
+ }
+
+ wireMap.put(revision, packageWires);
+
+ return wireMap;
+ }
+
+ private static void dumpRevisionPkgMap(Map<BundleRevision, Packages> revisionPkgMap)
+ {
+ System.out.println("+++BUNDLE REVISION PKG MAP+++");
+ for (Entry<BundleRevision, Packages> entry : revisionPkgMap.entrySet())
+ {
+ dumpRevisionPkgs(entry.getKey(), entry.getValue());
+ }
+ }
+
+ private static void dumpRevisionPkgs(BundleRevision revision, Packages packages)
+ {
+ System.out.println(revision
+ + " (" + ((revision.getWiring() != 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<Blame>> entry : packages.m_usedPkgs.entrySet())
+ {
+ System.out.println(" " + entry.getKey() + " - " + entry.getValue());
+ }
+ }
+
+ private static String toStringBlame(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++)
+ {
+ BundleRequirement req = blame.m_reqs.get(i);
+ sb.append(" ");
+ sb.append(req.getRevision().getSymbolicName());
+ sb.append(" [");
+ sb.append(req.getRevision().toString());
+ sb.append("]\n");
+ if (req.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
+ {
+ sb.append(" import: ");
+ }
+ else
+ {
+ sb.append(" require: ");
+ }
+ sb.append(((BundleRequirementImpl) req).getFilter().toString());
+ sb.append("\n |");
+ if (req.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
+ {
+ sb.append("\n export: ");
+ }
+ else
+ {
+ sb.append("\n provide: ");
+ }
+ if ((i + 1) < blame.m_reqs.size())
+ {
+ BundleCapability cap = Util.getSatisfyingCapability(
+ blame.m_reqs.get(i + 1).getRevision(),
+ (BundleRequirementImpl) blame.m_reqs.get(i));
+ if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
+ {
+ sb.append(BundleRevision.PACKAGE_NAMESPACE);
+ sb.append("=");
+ sb.append(cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE).toString());
+ BundleCapability usedCap;
+ if ((i + 2) < blame.m_reqs.size())
+ {
+ usedCap = Util.getSatisfyingCapability(
+ blame.m_reqs.get(i + 2).getRevision(),
+ (BundleRequirementImpl) blame.m_reqs.get(i + 1));
+ }
+ else
+ {
+ usedCap = Util.getSatisfyingCapability(
+ blame.m_cap.getRevision(),
+ (BundleRequirementImpl) blame.m_reqs.get(i + 1));
+ }
+ sb.append("; uses:=");
+ sb.append(usedCap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE));
+ }
+ else
+ {
+ sb.append(cap);
+ }
+ sb.append("\n");
+ }
+ else
+ {
+ BundleCapability export = Util.getSatisfyingCapability(
+ blame.m_cap.getRevision(),
+ (BundleRequirementImpl) blame.m_reqs.get(i));
+ sb.append(export.getNamespace());
+ sb.append("=");
+ sb.append(export.getAttributes().get(export.getNamespace()).toString());
+ if (export.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
+ && !export.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE)
+ .equals(blame.m_cap.getAttributes().get(
+ BundleRevision.PACKAGE_NAMESPACE)))
+ {
+ sb.append("; uses:=");
+ sb.append(blame.m_cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE));
+ sb.append("\n export: ");
+ sb.append(BundleRevision.PACKAGE_NAMESPACE);
+ sb.append("=");
+ sb.append(blame.m_cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE).toString());
+ }
+ sb.append("\n ");
+ sb.append(blame.m_cap.getRevision().getSymbolicName());
+ sb.append(" [");
+ sb.append(blame.m_cap.getRevision().toString());
+ sb.append("]");
+ }
+ }
+ }
+ else
+ {
+ sb.append(blame.m_cap.getRevision().toString());
+ }
+ return sb.toString();
+ }
+
+ private static class Packages
+ {
+ private final BundleRevision m_revision;
+ 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<Blame>> m_usedPkgs = new HashMap();
+
+ public Packages(BundleRevision revision)
+ {
+ m_revision = revision;
+ }
+ }
+
+ private static class Blame
+ {
+ public final BundleCapability m_cap;
+ public final List<BundleRequirement> m_reqs;
+
+ public Blame(BundleCapability cap, List<BundleRequirement> reqs)
+ {
+ m_cap = cap;
+ m_reqs = reqs;
+ }
+
+ @Override
+ public String toString()
+ {
+ return m_cap.getRevision()
+ + "." + m_cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE)
+ + (((m_reqs == null) || m_reqs.isEmpty())
+ ? " NO BLAME"
+ : " BLAMED ON " + m_reqs);
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return (o instanceof Blame) && m_reqs.equals(((Blame) o).m_reqs)
+ && m_cap.equals(((Blame) o).m_cap);
+ }
+ }
+}
\ No newline at end of file
Added: ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResolverWire.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResolverWire.java?rev=1464402&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResolverWire.java (added)
+++ ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResolverWire.java Thu Apr 4 09:43:34 2013
@@ -0,0 +1,49 @@
+/*
+ * 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.framework.resolver;
+
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+
+public interface ResolverWire
+{
+ /**
+ * Returns the importing bundle revision.
+ * @return The importing bundle revision.
+ **/
+ public BundleRevision getRequirer();
+ /**
+ * Returns the associated requirement from the importing bundle revision
+ * that resulted in the creation of this wire.
+ * @return
+ **/
+ public BundleRequirement getRequirement();
+ /**
+ * Returns the exporting bundle revision.
+ * @return The exporting bundle revision.
+ **/
+ public BundleRevision getProvider();
+ /**
+ * Returns the associated capability from the exporting bundle revision
+ * that satisfies the requirement of the importing bundle revision.
+ * @return
+ **/
+ public BundleCapability getCapability();
+}
\ No newline at end of file
Added: ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResolverWireImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResolverWireImpl.java?rev=1464402&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResolverWireImpl.java (added)
+++ ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResolverWireImpl.java Thu Apr 4 09:43:34 2013
@@ -0,0 +1,68 @@
+/*
+ * 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.framework.resolver;
+
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+
+class ResolverWireImpl implements ResolverWire
+{
+ private final BundleRevision m_requirer;
+ private final BundleRequirement m_req;
+ private final BundleRevision m_provider;
+ private final BundleCapability m_cap;
+
+ public ResolverWireImpl(
+ BundleRevision requirer, BundleRequirement req,
+ BundleRevision provider, BundleCapability cap)
+ {
+ m_requirer = requirer;
+ m_req = req;
+ m_provider = provider;
+ m_cap = cap;
+ }
+
+ public BundleRevision getRequirer()
+ {
+ return m_requirer;
+ }
+
+ public BundleRequirement getRequirement()
+ {
+ return m_req;
+ }
+
+ public BundleRevision getProvider()
+ {
+ return m_provider;
+ }
+
+ public BundleCapability getCapability()
+ {
+ return m_cap;
+ }
+
+ public String toString()
+ {
+ return m_req
+ + " -> "
+ + "[" + m_provider + "]";
+ }
+}
\ No newline at end of file
Added: ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResourceNotFoundException.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResourceNotFoundException.java?rev=1464402&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResourceNotFoundException.java (added)
+++ ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/resolver/ResourceNotFoundException.java Thu Apr 4 09:43:34 2013
@@ -0,0 +1,32 @@
+/*
+ * 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.framework.resolver;
+
+public class ResourceNotFoundException extends Exception
+{
+ public ResourceNotFoundException(String msg)
+ {
+ super(msg);
+ }
+
+ public ResourceNotFoundException(String msg, Throwable th)
+ {
+ super(msg, th);
+ }
+}
\ No newline at end of file
Added: ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/FelixConstants.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/FelixConstants.java?rev=1464402&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/FelixConstants.java (added)
+++ ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/FelixConstants.java Thu Apr 4 09:43:34 2013
@@ -0,0 +1,69 @@
+/*
+ * 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.framework.util;
+
+public interface FelixConstants extends org.osgi.framework.Constants
+{
+ String SYSTEM_BUNDLE_SYMBOLICNAME = "org.apache.felix.framework";
+ // Framework constants and values.
+ String FRAMEWORK_VERSION_VALUE = "1.5";
+ String FRAMEWORK_VENDOR_VALUE = "Apache Software Foundation";
+
+ // Framework constants and values.
+ String FELIX_VERSION_PROPERTY = "felix.version";
+
+ // Miscellaneous manifest constants.
+ String DIRECTIVE_SEPARATOR = ":=";
+ String ATTRIBUTE_SEPARATOR = "=";
+ String CLASS_PATH_SEPARATOR = ",";
+ String CLASS_PATH_DOT = ".";
+ String PACKAGE_SEPARATOR = ";";
+ String VERSION_SEGMENT_SEPARATOR = ".";
+ int VERSION_SEGMENT_COUNT = 3;
+ String BUNDLE_NATIVECODE_OPTIONAL = "*";
+
+ // Miscellaneous OSGi constants.
+ String BUNDLE_URL_PROTOCOL = "bundle";
+
+ // Miscellaneous framework configuration property names.
+ String FRAMEWORK_BUNDLECACHE_IMPL = "felix.bundlecache.impl";
+ String LOG_LEVEL_PROP = "felix.log.level";
+ String LOG_LOGGER_PROP = "felix.log.logger";
+ String SYSTEMBUNDLE_ACTIVATORS_PROP = "felix.systembundle.activators";
+ String BUNDLE_STARTLEVEL_PROP = "felix.startlevel.bundle";
+ String SERVICE_URLHANDLERS_PROP = "felix.service.urlhandlers";
+ String IMPLICIT_BOOT_DELEGATION_PROP = "felix.bootdelegation.implicit";
+ String BOOT_CLASSLOADERS_PROP = "felix.bootdelegation.classloaders";
+ String USE_LOCALURLS_PROP = "felix.jarurls";
+
+ // Missing OSGi constant for resolution directive.
+ String RESOLUTION_DYNAMIC = "dynamic";
+
+ // Start level-related constants.
+ int FRAMEWORK_INACTIVE_STARTLEVEL = 0;
+ int FRAMEWORK_DEFAULT_STARTLEVEL = 1;
+ int SYSTEMBUNDLE_DEFAULT_STARTLEVEL = 0;
+ int BUNDLE_DEFAULT_STARTLEVEL = 1;
+
+ // Miscellaneous properties values.
+ String FAKE_URL_PROTOCOL_VALUE = "location:";
+ String FELIX_EXTENSION_ACTIVATOR = "Felix-Activator";
+ int EAGER_ACTIVATION = 0;
+ int LAZY_ACTIVATION = 1;
+}
\ No newline at end of file
Added: ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/MapToDictionary.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/MapToDictionary.java?rev=1464402&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/MapToDictionary.java (added)
+++ ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/MapToDictionary.java Thu Apr 4 09:43:34 2013
@@ -0,0 +1,83 @@
+/*
+ * 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.framework.util;
+
+import java.util.*;
+
+
+/**
+ * This is a simple class that implements a <tt>Dictionary</tt>
+ * from a <tt>Map</tt>. The resulting dictionary is immutable.
+**/
+public class MapToDictionary extends Dictionary
+{
+ /**
+ * Map source.
+ **/
+ private Map m_map = null;
+
+ public MapToDictionary(Map map)
+ {
+ if (map == null)
+ {
+ throw new IllegalArgumentException("Source map cannot be null.");
+ }
+ m_map = map;
+ }
+
+ public Enumeration elements()
+ {
+ return Collections.enumeration(m_map.values());
+ }
+
+ public Object get(Object key)
+ {
+ return m_map.get(key);
+ }
+
+ public boolean isEmpty()
+ {
+ return m_map.isEmpty();
+ }
+
+ public Enumeration keys()
+ {
+ return Collections.enumeration(m_map.keySet());
+ }
+
+ public Object put(Object key, Object value)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object remove(Object key)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public int size()
+ {
+ return m_map.size();
+ }
+
+ public String toString()
+ {
+ return m_map.toString();
+ }
+}
\ No newline at end of file