diff options
author | Thomas Watson | 2007-02-22 00:02:30 +0000 |
---|---|---|
committer | Thomas Watson | 2007-02-22 00:02:30 +0000 |
commit | 8f125ae86a197d773e2853bee02ce3a5bd9ba7a7 (patch) | |
tree | f06f89bec241efefecab90f31c1d81f2d47ec6af /bundles/org.eclipse.osgi/resolver | |
parent | 4e207e02dd929a5e0bebfd706c2b77f8b3a90bdb (diff) | |
download | rt.equinox.framework-8f125ae86a197d773e2853bee02ce3a5bd9ba7a7.tar.gz rt.equinox.framework-8f125ae86a197d773e2853bee02ce3a5bd9ba7a7.tar.xz rt.equinox.framework-8f125ae86a197d773e2853bee02ce3a5bd9ba7a7.zip |
Bug 173436 Issues with adopting the uses directiveafter_bug173436
Diffstat (limited to 'bundles/org.eclipse.osgi/resolver')
7 files changed, 711 insertions, 702 deletions
diff --git a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/BundleConstraint.java b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/BundleConstraint.java index d74b601d1..0f933f41a 100644 --- a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/BundleConstraint.java +++ b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/BundleConstraint.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2004, 2005 IBM Corporation and others. + * Copyright (c) 2004, 2007 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,16 +10,12 @@ *******************************************************************************/ package org.eclipse.osgi.internal.module; -import java.util.ArrayList; import org.eclipse.osgi.service.resolver.*; /* * A companion to BundleSpecification from the state for use while resolving */ public class BundleConstraint extends ResolverConstraint { - // a list of matching bundles; multiple matches are only supported for fragment host constraints - private ArrayList matchingBundles; - BundleConstraint(ResolverBundle bundle, VersionConstraint bundleConstraint) { super(bundle, bundleConstraint); } @@ -29,41 +25,4 @@ public class BundleConstraint extends ResolverConstraint { return false; return ((BundleSpecification) constraint).isOptional(); } - - ResolverBundle getMatchingBundle() { - return (ResolverBundle) (matchingBundles != null && matchingBundles.size() > 0 ? matchingBundles.get(0) : null); - } - - ResolverBundle[] getMatchingBundles() { - if (matchingBundles == null || matchingBundles.size() == 0) - return null; - return (ResolverBundle[]) matchingBundles.toArray(new ResolverBundle[matchingBundles.size()]); - } - - void addMatchingBundle(ResolverBundle rb) { - if (matchingBundles == null) - matchingBundles = new ArrayList(1); // rare to have more than one - if (!matchingBundles.contains(rb)) - matchingBundles.add(rb); - } - - void removeMatchingBundle(ResolverBundle rb) { - if (matchingBundles == null) - return; - matchingBundles.remove(rb); - } - - void removeAllMatchingBundles() { - matchingBundles = null; - } - - void setMatchingBundle(ResolverBundle rb) { - removeAllMatchingBundles(); - if (rb != null) - addMatchingBundle(rb); - } - - boolean foundMatchingBundles() { - return matchingBundles == null ? false : matchingBundles.size() > 0; - } } diff --git a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/GroupingChecker.java b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/GroupingChecker.java index 02be81def..027ca2bad 100644 --- a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/GroupingChecker.java +++ b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/GroupingChecker.java @@ -1,387 +1,339 @@ /******************************************************************************* - * Copyright (c) 2004, 2006 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ + * Copyright (c) 2004, 2007 IBM Corporation and others. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: IBM Corporation - initial API and implementation + ******************************************************************************/ package org.eclipse.osgi.internal.module; import java.util.*; import org.eclipse.osgi.service.resolver.BundleSpecification; -import org.osgi.framework.Constants; /* * The GroupingChecker checks the 'uses' directive on exported packages for consistency */ public class GroupingChecker { - // Maps bundles to their exports; keyed by - // ResolverBundle -> exports HashMap - // the exports HashMap is keyed by - // ResolverExport -> Object[] {'uses' constraint ArrayList, transitive constraint cache} - HashMap bundles = new HashMap(); - private boolean checkCycles = false; + final PackageRoots nullPackageRoots = new PackageRoots(null, null); + // a mapping of bundles to their package roots; keyed by + // ResolverBundle -> HashMap of packages; keyed by + // package name -> PackageRoots[] + private HashMap bundles = new HashMap(); - // Gets all constraints for an exported package. - // this will perform transitive closure on the uses constraints. - // this method also will cache the transitive results if the bundle is resolved - private ResolverExport[] getConstraints(ResolverExport constrained) { - // check the cache first - Object[] cachedResults = getCachedConstraints(constrained); - if (cachedResults != null && cachedResults[1] != null) { - if (!constrained.getExporter().isResolved()) { - // need to make sure none of the current constraints are dropped - ResolverExport[] constraints = (ResolverExport[]) cachedResults[1]; - boolean dropped = false; - for (int i = 0; !dropped && i < constraints.length; i++) - dropped = constraints[i].isDropped(); - if (!dropped) - return constraints; - // otherwise recompute the transitive constraints below - } else { - // we have aready cached the results for this return the cached results - return (ResolverExport[]) cachedResults[1]; - } + /* + * This method fully populates a bundles package roots for the purpose of resolving + * a dynamic import. Package roots must be fully populated because we need all the + * roots to do proper uses constraint verification on a dynamic import supplier. + */ + public void populateRoots(ResolverBundle bundle) { + bundles.remove(bundle); + // process all requires + BundleConstraint[] requires = bundle.getRequires(); + for (int j = 0; j < requires.length; j++) { + ResolverBundle selectedSupplier = (ResolverBundle) requires[j].getSelectedSupplier(); + if (selectedSupplier != null) + isConsistentInternal(bundle, selectedSupplier, new ArrayList(1), true); } - ArrayList resultlist = getConstraintsList(constrained); - ResolverExport[] results = (ResolverExport[]) resultlist.toArray(new ResolverExport[resultlist.size()]); - if (!checkCycles || constrained.getExporter().isResolved()) { - // if the bundle is resolved or we are not checking cycles then we can add the results to the cached results - if (cachedResults == null) - cachedResults = createConstraintsCache(constrained); // should have been created by getConstraintsList - cachedResults[1] = results; + // process all imports + ResolverImport[] imports = bundle.getImportPackages(); + for (int j = 0; j < imports.length; j++) { + ResolverExport selectedSupplier = (ResolverExport) imports[j].getSelectedSupplier(); + if (selectedSupplier != null) + isConsistentInternal(bundle, selectedSupplier, true); } - return results; - } - - private Object[] getCachedConstraints(ResolverExport constrained) { - // get the cached constraints for this export - HashMap exports = (HashMap) bundles.get(constrained.getExporter()); - return exports == null ? null : (Object[]) exports.get(constrained); - } - - // Gets all constraints for an exported package - // same as getConstraints only the raw ArrayList is returned and - // the cache is not consulted. - private ArrayList getConstraintsList(ResolverExport constrained) { - ArrayList results = new ArrayList(); - getTransitiveConstraints(constrained, results); - return results; } - // adds the transitive 'uses' constraints for the specified constrained ResolverExport - // to the results ArrayList. - private void getTransitiveConstraints(ResolverExport constrained, ArrayList results) { - if (constrained.isDropped()) - return; - // first get any constraints that may come from required bundle exports - ResolverExport[] constrainedRoots = constrained.getRoots(); - for (int i = 0; i < constrainedRoots.length; i++) - // only search for transitive constraints on other exports (i.e. from required bundles) - if (constrainedRoots[i] != constrained) - getTransitiveConstraints(constrainedRoots[i], results); - // now get the constraints that are specified by this export - Object[] cachedConstraints = getCachedConstraints(constrained); - ArrayList constraints = (ArrayList) (cachedConstraints != null ? cachedConstraints[0] : null); - if (constraints == null) - return; - for (Iterator iter = constraints.iterator(); iter.hasNext();) { - Object constraint = iter.next(); - ResolverExport[] moreConstraints = null; - if (constraint instanceof ResolverExport) { - // the constraint is to another export from this bundle - ResolverExport export = (ResolverExport) constraint; - // get the roots from this export; incase it is split from a required bundle - moreConstraints = export.getRoots(); - } else if (constraint instanceof ResolverImport) { - // the constraint is to an imported package - ResolverImport imp = (ResolverImport) constraint; - // if the import is resolved then we need to add the constraints of the roots from the export - if (imp.getMatchingExport() != null) - moreConstraints = imp.getMatchingExport().getRoots(); - } else if (constraint instanceof UsesRequiredExport) { - // the constraint is on a package from a required bundle; get the roots from the required bundles - moreConstraints = ((UsesRequiredExport) constraint).getRoots(); - } - if (moreConstraints != null) - // now add each root as a constraint - for (int i = 0; i < moreConstraints.length; i++) - if (!results.contains(moreConstraints[i])) { - results.add(moreConstraints[i]); - if (moreConstraints[i] != constraint) - // add the constraints for each root - getTransitiveConstraints(moreConstraints[i], results); - } - } + /* + * Verifies the uses constraint consistency for the requiringBundle with the possible matching bundle. + * If an inconsistency is found the export inconsistency is returned; otherwise null is returned + */ + public ResolverExport isConsistent(ResolverBundle requiringBundle, ResolverBundle matchingBundle) { + ResolverExport inConsistentExport = isConsistentInternal(requiringBundle, matchingBundle, new ArrayList(1), false); + if (inConsistentExport != null) + return inConsistentExport; + return null; } - // creates the Object[] used to cache constraint definitions and transitive - // closure results. - private Object[] createConstraintsCache(ResolverExport constrained) { - HashMap exports = (HashMap) bundles.get(constrained.getExporter()); - if (exports == null) { - exports = new HashMap(); - bundles.put(constrained.getExporter(), exports); + private ResolverExport isConsistentInternal(ResolverBundle requiringBundle, ResolverBundle matchingBundle, ArrayList visited, boolean dynamicImport) { + // needed to prevent endless cycles + if (visited.contains(matchingBundle)) + return null; + visited.add(matchingBundle); + // check that the packages exported by the matching bundle are consistent + ResolverExport[] matchingExports = matchingBundle.getExportPackages(); + for (int i = 0; i < matchingExports.length; i++) { + if (matchingExports[i].isDropped()) + continue; + if (!isConsistentInternal(requiringBundle, matchingExports[i], dynamicImport)) + return matchingExports[i]; } - Object[] constraints = (Object[]) exports.get(constrained); - if (constraints == null) { - constraints = new Object[2]; - exports.put(constrained, constraints); + // check that the packages from reexported bundles are consistent + BundleConstraint[] supplierRequires = matchingBundle.getRequires(); + for (int j = 0; j < supplierRequires.length; j++) { + ResolverBundle reexported = (ResolverBundle) supplierRequires[j].getSelectedSupplier(); + if (reexported == null || !((BundleSpecification) supplierRequires[j].getVersionConstraint()).isExported()) + continue; + ResolverExport inConsistentExport = isConsistentInternal(requiringBundle, reexported, visited, dynamicImport); + if (inConsistentExport != null) + return inConsistentExport; } - return constraints; - } - - // creates an empty list to hold constraints for the specified export - // if a list already exists then a new list is NOT created. - private ArrayList createConstraints(ResolverExport constrained) { - Object[] constraints = createConstraintsCache(constrained); - if (constraints[0] == null) - constraints[0] = new ArrayList(); - return (ArrayList) constraints[0]; - } - - // adds a 'uses' constraint to a ResolverExport. A new 'uses' constraint list - // is created if one does not exist. - private void addConstraint(ResolverExport constrained, Object constraint) { - ArrayList list = createConstraints(constrained); - if (!list.contains(constraint)) - list.add(constraint); - } - - private void addConstraint(ArrayList export, Object constraint) { - for (Iterator iExport = export.iterator(); iExport.hasNext();) - addConstraint((ResolverExport) iExport.next(), constraint); + return null; } - // removes all constraints specified by this bundles exports - void removeAllExportConstraints(ResolverBundle bundle) { - bundles.remove(bundle); + /* + * Verifies the uses constraint consistency for the importingBundle with the possible matching export. + * If an inconsistency is found the export returned; otherwise null is returned + */ + public ResolverExport isConsistent(ResolverBundle importingBundle, ResolverExport matchingExport) { + if (!isConsistentInternal(importingBundle, matchingExport, false)) + return matchingExport; + return null; } - // checks the 'uses' consistency of the required BundleConstraint with other required - // BundleConstraints of the specified ResolverBundle - ResolverBundle isConsistent(BundleConstraint req, ResolverBundle bundle) { - BundleConstraint[] requires = req.getBundle().getRequires(); - ArrayList visited = new ArrayList(requires.length); - for (int i = 0; i < requires.length; i++) { - ResolverBundle match = requires[i].getMatchingBundle(); - if (match == bundle || match == null) - continue; // the constraint has not been resolved or is to ourselves - // check the consistency of each exported package from the new required match - ResolverExport[] exports = match.getSelectedExports(); - for (int j = 0; j < exports.length; j++) - if (checkReqExpConflict(exports[j], getConstraints(exports[j]), bundle, visited) != null) - return match; - } + /* + * Verifies the uses constraint consistency for the importingBundle with the possible dynamioc matching export. + * If an inconsistency is found the export returned; otherwise null is returned. + * Dynamic imports must perform extra checks to ensure that existing wires to package roots are + * consistent with the possible matching dynamic export. + */ + public ResolverExport isDynamicConsistent(ResolverBundle importingBundle, ResolverExport matchingExport) { + if (!isConsistentInternal(importingBundle, matchingExport, true)) + return matchingExport; return null; } - // checks the 'uses consistency of the imported ResolverImport with specified - // ResolverExport. This will also check 'uses' consistency with other imported packages - // and other required bundles of the importing bundle. - ResolverExport isConsistent(ResolverImport imp, ResolverExport exp) { - ResolverExport[] expConstraints = getConstraints(exp); - // Check imports are consistent - ResolverImport[] imports = imp.getBundle().getImportPackages(); - for (int i = 0; i < imports.length; i++) { - // Check new wiring constraints against constraints from previous wirings - ResolverExport conflict = checkImpExpConflict(imp, imports[i].getMatchingExport(), exp, expConstraints); - if (conflict != null) - return conflict; - } - // Check new wiring against required exports - BundleConstraint[] requires = imp.getBundle().getRequires(); - ArrayList visited = new ArrayList(requires.length); // visited list prevents recursive cycles - for (int i = 0; i < requires.length; i++) { - ResolverExport conflict = checkReqExpConflict(exp, expConstraints, requires[i].getMatchingBundle(), visited); - if (conflict != null) - return conflict; - } - // No clash, so return null - return null; + private boolean isConsistentInternal(ResolverBundle importingBundle, ResolverExport matchingExport, boolean dyanamicImport) { + PackageRoots exportingRoots = getPackageRoots(matchingExport.getExporter(), matchingExport.getName(), null); + // check that the exports uses packages are consistent with existing package roots + if (!exportingRoots.isConsistentClassSpace(importingBundle, null)) + return false; + if (!dyanamicImport) + return true; + // for dynamic imports we must check that each existing root is consistent with the possible matching export + PackageRoots importingRoots = getPackageRoots(importingBundle, matchingExport.getName(), null); + HashMap importingPackages = (HashMap) bundles.get(importingBundle); + if (importingPackages != null) + for (Iterator allImportingPackages = importingPackages.values().iterator(); allImportingPackages.hasNext();) { + PackageRoots roots = (PackageRoots) allImportingPackages.next(); + if (roots != importingRoots && !roots.isConsistentClassSpace(exportingRoots, null)) + return false; + } + return true; } - // checks the 'uses' constraints ResolverImport's matching export with the constraints - // of the candidate ResolverExport. If a conflict exists then the conflict is returned. - private ResolverExport checkImpExpConflict(ResolverImport imp, ResolverExport existing, ResolverExport candidate, ResolverExport[] candConstraints) { - if (existing == null) - return null; // for an import that has not been resolved - // check the constraints of the existing wire against the candidate only - // if the existing wire is not the candidate - if (existing != candidate) { - ResolverExport[] impConstraints = getConstraints(existing); - for (int j = 0; j < impConstraints.length; j++) - if (isConflict(candidate, impConstraints[j])) - return existing; + /* + * returns package roots for a specific package name for a specific bundle + */ + PackageRoots getPackageRoots(ResolverBundle bundle, String packageName, ArrayList visited) { + HashMap packages = (HashMap) bundles.get(bundle); + if (packages == null) { + packages = new HashMap(5); + bundles.put(bundle, packages); } - // Check existing wirings against constraints from candidate wiring - for (int i = 0; i < candConstraints.length; i++) { - // check if we export this package - if (imp.getBundle() != candConstraints[i].getExporter()) { - // there is no need to do this check if the candidate constaint is from ourselves - ResolverExport[] importerExp = imp.getBundle().getExports(candConstraints[i].getName()); - if (importerExp.length > 0) { - ResolverImport alsoImported = imp.getBundle().getImport(candConstraints[i].getName()); - // only do the extra check if we do not import the package or if we do and the import is resolved - if (alsoImported == null || alsoImported.getMatchingExport() != null) - for (int j = 0; j < importerExp.length; j++) - if (!importerExp[j].isDropped() && !isOnRoot(importerExp[j].getRoots(), candConstraints[i])) - // if we export the package and it is not the same one then we have a clash - return existing; - } - } - // check the existing resolved import against the candidate constraint - if (isConflict(existing, candConstraints[i])) - return existing; + PackageRoots packageRoots = (PackageRoots) packages.get(packageName); + if (packageRoots == null) { + packageRoots = createPackageRoots(bundle, packageName, visited == null ? new ArrayList(1) : visited); + packages.put(packageName, packageRoots); } - return null; + return packageRoots != null ? packageRoots : nullPackageRoots; } - // checks the 'uses' constraints ResolverExport with the constraints of the ResolverExports - // from the specified ResolverBundle. If a conflict exists then the conflict is returned. - private ResolverExport checkReqExpConflict(ResolverExport exp, ResolverExport expConstraints[], ResolverBundle bundle, ArrayList visited) { - if (bundle == null) + private PackageRoots createPackageRoots(ResolverBundle bundle, String packageName, ArrayList visited) { + if (visited.contains(bundle)) return null; - if (visited.contains(bundle)) // return if we visited this bundle; prevent endless loops - return null; - visited.add(bundle); // mark as visited - // check the consistency of all the selected exports - ResolverExport[] exports = bundle.getSelectedExports(); - for (int i = 0; i < exports.length; i++) { - // first check the constraints of the 'exp' with the exported packages of the 'bundle' - for (int j = 0; j < expConstraints.length; j++) - if (isConflict(exports[i], expConstraints[j])) - return exp; - // next check the constraints of each exported package of the 'bundle' with the 'exp' - ResolverExport[] constraints = getConstraints(exports[i]); - for (int j = 0; j < constraints.length; j++) - if (isConflict(exp, constraints[j])) - return exp; + visited.add(bundle); // prevent endless cycles + // check imports + ResolverImport imported = bundle.getImport(packageName); + if (imported != null && imported.getSelectedSupplier() != null) { + // make sure we are not resolved to our own import + ResolverExport selectedExport = (ResolverExport) imported.getSelectedSupplier(); + if (selectedExport.getExporter() != bundle) { + // found resolved import; get the roots from the resolved exporter; + // this is all the roots if the package is imported + return getPackageRoots(selectedExport.getExporter(), packageName, visited); + } } - // check the consistency of all the required bundles which we reexport + // check if the bundle exports the package + ResolverExport export = bundle.getExport(packageName); + ArrayList roots = new ArrayList(0); + // check roots from required bundles BundleConstraint[] requires = bundle.getRequires(); - for (int i = 0; i < requires.length; i++) - if (((BundleSpecification) requires[i].getVersionConstraint()).isExported()) { - ResolverExport conflict = checkReqExpConflict(exp, expConstraints, requires[i].getMatchingBundle(), visited); - if (conflict != null) - return exp; + for (int i = 0; i < requires.length; i++) { + ResolverBundle supplier = (ResolverBundle) requires[i].getSelectedSupplier(); + if (supplier == null) + continue; // no supplier, probably optional + if (supplier.getExport(packageName) != null) { + // the required bundle exports the package; get the package roots from it + PackageRoots requiredRoots = getPackageRoots(supplier, packageName, visited); + if (requiredRoots != null) + roots.add(requiredRoots); + } else { + // the bundle does not export the package; but it may reexport another bundle that does + BundleConstraint[] supplierRequires = supplier.getRequires(); + for (int j = 0; j < supplierRequires.length; j++) { + ResolverBundle reexported = (ResolverBundle) supplierRequires[j].getSelectedSupplier(); + if (reexported == null || !((BundleSpecification) supplierRequires[j].getVersionConstraint()).isExported()) + continue; + if (reexported.getExport(packageName) != null) { + // the reexported bundle exports the package; get the package roots from it + PackageRoots reExportedRoots = getPackageRoots(reexported, packageName, visited); + if (reexported != null) + roots.add(reExportedRoots); + } + } } - return null; + } + if (export != null || roots.size() > 1) { + // in this case we cannot share the package roots object; must create one specific for this bundle + PackageRoots result = new PackageRoots(packageName, bundle); + // first merge all the roots from required bundles + for (Iterator iRoots = roots.iterator(); iRoots.hasNext();) + result.merge((PackageRoots) iRoots.next()); + if (export != null) + // always add this bundles export to the end if it exports the package + result.addRoot(export); + return result; + } + return (PackageRoots) (roots.size() == 0 ? nullPackageRoots : roots.get(0)); } - // returns true if the candidateRoot is NOT a root of the candidate ResolverExport - private boolean isConflict(ResolverExport candidate, ResolverExport candidateRoot) { - return candidateRoot.getExporter().isResolvable() && candidateRoot.getName().equals(candidate.getName()) && !isOnRoot(candidate.getRoots(), candidateRoot); + public void clear() { + bundles.clear(); } - // checks that the specified ResolverExport is contained in the list of roots - private boolean isOnRoot(ResolverExport[] roots, ResolverExport re) { - for (int i = 0; i < roots.length; i++) - // check the exporter; this handles multple exports of the same package name from a bundle - if (roots[i].getExporter() == re.getExporter()) - return true; - if (roots.length == 1 && !roots[0].getExportPackageDescription().isRoot()) - return true; // this is for a reexporter that does not have the import satisfied yet - return false; + public void remove(ResolverBundle rb) { + bundles.remove(rb); } - // Add initial 'uses' constraints for a list of bundles - void addInitialGroupingConstraints(ResolverBundle initBundle) { - if (bundles.containsKey(initBundle)) - return; // already processed - // for each export; add it uses constraints - ResolverExport[] exports = initBundle.getExportPackages(); - for (int j = 0; j < exports.length; j++) { - ArrayList export = new ArrayList(); - export.add(exports[j]); - addInitialGroupingConstraints(export, null); - } - if (bundles.get(initBundle) == null) - bundles.put(initBundle, null); // mark this bundle as processed - } + private class PackageRoots { + private String name; + private ResolverBundle bundle; + private ResolverExport[] roots; - // Add constraints from other exports (due to 'uses' being transitive) - private void addInitialGroupingConstraints(ArrayList exports, ResolverExport constraint) { - // pop the intitial export off as the current export we are checking - ResolverExport cur = (ResolverExport) exports.get(0); - if (cur == constraint) - return; - if (constraint == null) { - if (getCachedConstraints(cur) != null) - // we have already processed the current export; just return - return; - constraint = cur; + PackageRoots(String name, ResolverBundle bundle) { + this.name = name; + this.bundle = bundle; } - String[] uses = (String[]) constraint.getExportPackageDescription().getDirective(Constants.USES_DIRECTIVE); - if (uses == null) - return; - boolean constraintAdded = false; - if (!exports.contains(constraint) && getCachedConstraints(constraint) == null) { - // the constraints have not been evaluated for this export add it to the list to process - exports.add(constraint); - constraintAdded = true; + + public boolean hasRoots() { + return roots != null && roots.length > 0; } - ResolverBundle exporter = cur.getExporter(); - for (int i = 0; i < uses.length; i++) { - ResolverExport[] constraintExports = exporter.getExports(uses[i]); - for (int j = 0; j < constraintExports.length; j++) { - // Check if the constraint has already been added so we don't recurse infinitely - if (!createConstraints(cur).contains(constraintExports[j])) { - addConstraint(exports, constraintExports[j]); - addInitialGroupingConstraints(exports, constraintExports[j]); - } + + public void addRoot(ResolverExport export) { + if (roots == null) { + roots = new ResolverExport[] {export}; + return; + } + // need to do an extra check to make sure we are not adding the same package name + // from multiple versions of the same bundle + String exportBSN = export.getExporter().getName(); + if (exportBSN != null) { + // first one wins + for (int i = 0; i < roots.length; i++) + if (exportBSN.equals(roots[i].getExporter().getName())) + return; + } + if (!contains(export, roots)) { + ResolverExport[] newRoots = new ResolverExport[roots.length + 1]; + System.arraycopy(roots, 0, newRoots, 0, roots.length); + newRoots[roots.length] = export; + roots = newRoots; } - ResolverImport constraintImport = exporter.getImport(uses[i]); - if (constraintImport != null && !constraintImport.isDynamic()) - addConstraint(exports, constraintImport); - if (constraintExports.length == 0 && (constraintImport == null || constraintImport.isDynamic())) - addConstraint(exports, new UsesRequiredExport(constraint, uses[i])); } - if (constraintAdded) - // we have finished processing all the uses for the constraint; remove it from the list to process - exports.remove(constraint); - } - // Used to hold a 'uses' constraint on a package that is accessed through a required bundle - private class UsesRequiredExport { - // the exported package which 'uses' a package from a required bundle - private ResolverExport export; - // the name of the package which is 'used' - private String usesName; + private boolean contains(ResolverExport export, ResolverExport[] exports) { + for (int i = 0; i < exports.length; i++) + if (exports[i] == export) + return true; + return false; + } - UsesRequiredExport(ResolverExport export, String usesName) { - this.export = export; - this.usesName = usesName; + public void merge(PackageRoots packageRoots) { + if (packageRoots == null || packageRoots.roots == null) + return; + int size = packageRoots.roots.length; + for (int i = 0; i < size; i++) + addRoot(packageRoots.roots[i]); } - public ResolverExport[] getRoots() { - ArrayList results = new ArrayList(1); // rare to have more than 1 - BundleConstraint[] requires = export.getExporter().getRequires(); - for (int i = 0; i < requires.length; i++) { - if (requires[i].getMatchingBundle() == null) + public boolean isConsistentClassSpace(ResolverBundle importingBundle, ArrayList visited) { + if (roots == null) + return true; + int size = roots.length; + for (int i = 0; i < size; i++) { + ResolverExport root = roots[i]; + String[] uses = root.getUsesDirective(); + if (uses == null) continue; - ResolverExport requiredExport = requires[i].getMatchingBundle().getExport(usesName); - if (requiredExport != null && !requiredExport.isDropped()) { - ResolverExport[] roots = requiredExport.getRoots(); - for (int j = 0; j < roots.length; j++) - if (!results.contains(requiredExport)) - results.add(roots[j]); + if (visited == null) + visited = new ArrayList(1); + if (visited.contains(this)) + return true; + visited.add(this); + for (int j = 0; j < uses.length; j++) { + if (uses[j].equals(root.getName())) + continue; + PackageRoots thisUsedRoots = getPackageRoots(root.getExporter(), uses[j], null); + PackageRoots importingUsedRoots = getPackageRoots(importingBundle, uses[j], null); + if (thisUsedRoots == importingUsedRoots) + return true; + if (thisUsedRoots != nullPackageRoots && importingUsedRoots != nullPackageRoots) + if (!(subSet(thisUsedRoots.roots, importingUsedRoots.roots) || subSet(importingUsedRoots.roots, thisUsedRoots.roots))) + return false; + // need to check the usedRoots consistency for transitive closure + if (!thisUsedRoots.isConsistentClassSpace(importingBundle, visited)) + return false; } } - return (ResolverExport[]) results.toArray(new ResolverExport[results.size()]); + return true; } - public boolean equals(Object o) { - if (!(o instanceof UsesRequiredExport)) - return false; - return ((UsesRequiredExport) o).export.getExporter() == export.getExporter() && usesName.equals(((UsesRequiredExport) o).usesName); + public boolean isConsistentClassSpace(PackageRoots exportingRoots, ArrayList visited) { + if (roots == null) + return true; + int size = roots.length; + for (int i = 0; i < size; i++) { + ResolverExport root = roots[i]; + String[] uses = root.getUsesDirective(); + if (uses == null) + continue; + if (visited == null) + visited = new ArrayList(1); + if (visited.contains(this)) + return true; + visited.add(this); + for (int j = 0; j < uses.length; j++) { + if (uses[j].equals(root.getName()) || !uses[j].equals(exportingRoots.name)) + continue; + PackageRoots thisUsedRoots = getPackageRoots(root.getExporter(), uses[j], null); + PackageRoots exportingUsedRoots = getPackageRoots(exportingRoots.bundle, uses[j], null); + if (thisUsedRoots == exportingRoots) + return true; + if (thisUsedRoots != nullPackageRoots && exportingUsedRoots != nullPackageRoots) + if (!(subSet(thisUsedRoots.roots, exportingUsedRoots.roots) || subSet(exportingUsedRoots.roots, thisUsedRoots.roots))) + return false; + // need to check the usedRoots consistency for transitive closure + if (!thisUsedRoots.isConsistentClassSpace(exportingRoots, visited)) + return false; + } + } + return true; } - } - public void setCheckCycles(boolean checkCycles) { - this.checkCycles = checkCycles; + // TODO this is a behavioral change; before we only required 1 supplier to match; now roots must be subsets + private boolean subSet(ResolverExport[] superSet, ResolverExport[] subSet) { + for (int i = 0; i < subSet.length; i++) { + boolean found = false; + for (int j = 0; j < superSet.length; j++) + if (subSet[i] == superSet[j]) { + found = true; + break; + } + if (!found) + return false; + } + return true; + } } } diff --git a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverBundle.java b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverBundle.java index 2d805d787..6a7caa076 100644 --- a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverBundle.java +++ b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverBundle.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2004, 2006 IBM Corporation and others. + * Copyright (c) 2004, 2007 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -116,19 +116,17 @@ public class ResolverBundle extends VersionSupplier { return (ResolverExport[]) results.toArray(new ResolverExport[results.size()]); } - void clearWires(boolean clearUnresolvable) { + void clearWires() { ResolverImport[] allImports = getImportPackages(); - for (int i = 0; i < allImports.length; i++) { - allImports[i].setMatchingExport(null); - if (clearUnresolvable) - allImports[i].clearUnresolvableWirings(); - } + for (int i = 0; i < allImports.length; i++) + allImports[i].clearPossibleSuppliers(); if (host != null) - host.removeAllMatchingBundles(); + host.clearPossibleSuppliers(); + BundleConstraint[] allRequires = getRequires(); for (int i = 0; i < allRequires.length; i++) - allRequires[i].setMatchingBundle(null); + allRequires[i].clearPossibleSuppliers(); GenericConstraint[] allGenericRequires = getGenericRequires(); for (int i = 0; i < allGenericRequires.length; i++) @@ -309,7 +307,7 @@ public class ResolverBundle extends VersionSupplier { return new ResolverExport[0]; // cannot attach to fragments; if (!getBundle().attachFragments() || (isResolved() && !getBundle().dynamicFragments())) return new ResolverExport[0]; // host is restricting attachment - if (fragment.getHost().getMatchingBundles() != null && !((HostSpecification) fragment.getHost().getVersionConstraint()).isMultiHost()) + if (fragment.getHost().getNumPossibleSuppliers() > 0 && !((HostSpecification) fragment.getHost().getVersionConstraint()).isMultiHost()) return new ResolverExport[0]; // fragment is restricting attachment ImportPackageSpecification[] newImports = fragment.getBundle().getImportPackages(); @@ -327,7 +325,7 @@ public class ResolverBundle extends VersionSupplier { if (fragments.contains(fragment)) return new ResolverExport[0]; fragments.add(fragment); - fragment.getHost().addMatchingBundle(this); + fragment.getHost().addPossibleSupplier(this); if (newImports.length > 0) { ArrayList hostImports = new ArrayList(newImports.length); @@ -373,7 +371,7 @@ public class ResolverBundle extends VersionSupplier { boolean result = false; for (int i = 0; i < newImports.length; i++) { ResolverImport hostImport = getImport(newImports[i].getName()); - ResolverExport resolvedExport = hostImport == null ? null : hostImport.getMatchingExport(); + ResolverExport resolvedExport = (ResolverExport) (hostImport == null ? null : hostImport.getSelectedSupplier()); if ((resolvedExport == null && isResolved()) || (resolvedExport != null && !newImports[i].isSatisfiedBy(resolvedExport.getExportPackageDescription()))) { result = true; resolver.getState().addResolverError(fragment, ResolverError.FRAGMENT_CONFLICT, newImports[i].toString(), newImports[i]); @@ -381,7 +379,7 @@ public class ResolverBundle extends VersionSupplier { } for (int i = 0; i < newRequires.length; i++) { BundleConstraint hostRequire = getRequire(newRequires[i].getName()); - ResolverBundle resolvedRequire = hostRequire == null ? null : hostRequire.getMatchingBundle(); + ResolverBundle resolvedRequire = (ResolverBundle) (hostRequire == null ? null : hostRequire.getSelectedSupplier()); if ((resolvedRequire == null && isResolved()) || (resolvedRequire != null && !newRequires[i].isSatisfiedBy(resolvedRequire.getBundle()))) { result = true; resolver.getState().addResolverError(fragment, ResolverError.FRAGMENT_CONFLICT, newRequires[i].toString(), newRequires[i]); @@ -411,7 +409,7 @@ public class ResolverBundle extends VersionSupplier { return new ResolverExport[0]; fragment.setNewFragmentExports(false); - fragment.getHost().removeMatchingBundle(this); + fragment.getHost().removePossibleSupplier(this); ArrayList fragImports = (ArrayList) fragmentImports.remove(fragment.bundleID); ArrayList fragRequires = (ArrayList) fragmentRequires.remove(fragment.bundleID); ArrayList removedExports = (ArrayList) fragmentExports.remove(fragment.bundleID); @@ -436,7 +434,7 @@ public class ResolverBundle extends VersionSupplier { for (Iterator iOldImports = fragImports.iterator(); iOldImports.hasNext();) { ResolverImport oldImport = (ResolverImport) iOldImports.next(); if (newImport.getName().equals(oldImport.getName())) - newImport.setMatchingExport(oldImport.getMatchingExport()); + newImport.setPossibleSuppliers(oldImport.getPossibleSuppliers()); } } ArrayList newRequires = (ArrayList) fragmentRequires.get(remainingFrags[i].bundleID); @@ -446,7 +444,7 @@ public class ResolverBundle extends VersionSupplier { for (Iterator iOldRequires = fragRequires.iterator(); iOldRequires.hasNext();) { BundleConstraint oldRequire = (BundleConstraint) iOldRequires.next(); if (newRequire.getName().equals(oldRequire.getName())) - newRequire.setMatchingBundle(oldRequire.getMatchingBundle()); + newRequire.setPossibleSuppliers(oldRequire.getPossibleSuppliers()); } } } diff --git a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverConstraint.java b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverConstraint.java index 6edb7ca9f..7f3040e26 100644 --- a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverConstraint.java +++ b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverConstraint.java @@ -1,13 +1,11 @@ /******************************************************************************* - * Copyright (c) 2005 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2005, 2007 IBM Corporation and others. All rights reserved. This + * program and the accompanying materials are made available under the terms of + * the Eclipse Public License v1.0 which accompanies this distribution, and is + * available at http://www.eclipse.org/legal/epl-v10.html * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ + * Contributors: IBM Corporation - initial API and implementation + ******************************************************************************/ package org.eclipse.osgi.internal.module; import org.eclipse.osgi.service.resolver.BundleDescription; @@ -17,8 +15,10 @@ import org.eclipse.osgi.service.resolver.VersionConstraint; * A companion to VersionConstraint from the state used while resolving */ public abstract class ResolverConstraint { - ResolverBundle bundle; - VersionConstraint constraint; + final protected ResolverBundle bundle; + final protected VersionConstraint constraint; + private VersionSupplier[] possibleSuppliers; + private int selectedSupplierIndex = 0; ResolverConstraint(ResolverBundle bundle, VersionConstraint constraint) { this.bundle = bundle; @@ -63,4 +63,80 @@ public abstract class ResolverConstraint { // returns whether this constraint is optional abstract boolean isOptional(); + + public void setPossibleSuppliers(VersionSupplier[] possibleSuppliers) { + this.possibleSuppliers = possibleSuppliers; + } + + void addPossibleSupplier(VersionSupplier supplier) { + if (supplier == null) + return; + // we hope multiple suppliers are rare so do simple array expansion here. + if (possibleSuppliers == null) { + possibleSuppliers = new VersionSupplier[] {supplier}; + return; + } + VersionSupplier[] newSuppliers = new VersionSupplier[possibleSuppliers.length + 1]; + System.arraycopy(possibleSuppliers, 0, newSuppliers, 0, possibleSuppliers.length); + newSuppliers[possibleSuppliers.length] = supplier; + possibleSuppliers = newSuppliers; + } + + public void removePossibleSupplier(VersionSupplier supplier) { + if (possibleSuppliers == null || supplier == null) + return; + int index = -1; + for (int i = 0; i < possibleSuppliers.length; i++) { + if (possibleSuppliers[i] == supplier) { + index = i; + break; + } + } + if (index >= 0) { + if (possibleSuppliers.length == 1) { + possibleSuppliers = null; + return; + } + VersionSupplier[] newSuppliers = new VersionSupplier[possibleSuppliers.length - 1]; + System.arraycopy(possibleSuppliers, 0, newSuppliers, 0, index); + if (index < possibleSuppliers.length - 1) + System.arraycopy(possibleSuppliers, index + 1, newSuppliers, index, possibleSuppliers.length - index - 1); + } + } + + int getNumPossibleSuppliers() { + if (possibleSuppliers == null) + return 0; + return possibleSuppliers.length; + } + + boolean selectNextSupplier() { + if (possibleSuppliers == null || selectedSupplierIndex >= possibleSuppliers.length) + return false; + selectedSupplierIndex += 1; + return selectedSupplierIndex < possibleSuppliers.length; + } + + VersionSupplier getSelectedSupplier() { + if (possibleSuppliers == null || selectedSupplierIndex >= possibleSuppliers.length) + return null; + return possibleSuppliers[selectedSupplierIndex]; + } + + void setSelectedSupplier(int selectedSupplier) { + this.selectedSupplierIndex = selectedSupplier; + } + + int getSelectedSupplierIndex() { + return this.selectedSupplierIndex; + } + + VersionSupplier[] getPossibleSuppliers() { + return possibleSuppliers; + } + + void clearPossibleSuppliers() { + possibleSuppliers = null; + selectedSupplierIndex = 0; + } } diff --git a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverExport.java b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverExport.java index 0c329b164..49ad45483 100644 --- a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverExport.java +++ b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverExport.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2004, 2005 IBM Corporation and others. + * Copyright (c) 2004, 2007 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,9 +10,9 @@ *******************************************************************************/ package org.eclipse.osgi.internal.module; -import java.util.ArrayList; import org.eclipse.osgi.service.resolver.BundleDescription; import org.eclipse.osgi.service.resolver.ExportPackageDescription; +import org.osgi.framework.Constants; /* * A companion to ExportPackageDescription from the state used while resolving. @@ -37,36 +37,7 @@ public class ResolverExport extends VersionSupplier { return resolverBundle; } - /* - * returns a list of roots for this export. The returned list will include and exports - * which the exporting bundle may get access to from imported packages or required bundles. - */ - ResolverExport[] getRoots() { - ArrayList results = new ArrayList(1); // usually only one root - addRoots(results); - return (ResolverExport[]) results.toArray(new ResolverExport[results.size()]); - } - - /* - * Adds roots for this export to the specified roots list. - */ - private void addRoots(ArrayList roots) { - if (roots.contains(this)) - return; - ResolverImport ri = getExporter().getImport(getName()); - if (ri != null && ri.getMatchingExport() != null && ri.getMatchingExport() != this) { - ri.getMatchingExport().addRoots(roots); - return; - } - // always add to the front of the list - roots.add(0, this); - BundleConstraint[] requires = getExporter().getRequires(); - for (int i = 0; i < requires.length; i++) { - if (requires[i].getMatchingBundle() == null) - continue; - ResolverExport requiredExport = requires[i].getBundle().getExport(getName()); - if (requiredExport != null && !requiredExport.isDropped()) - requiredExport.addRoots(roots); - } + String[] getUsesDirective() { + return (String[]) getExportPackageDescription().getDirective(Constants.USES_DIRECTIVE); } } diff --git a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverImpl.java b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverImpl.java index b5a827d8e..32c82f4f1 100644 --- a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverImpl.java +++ b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverImpl.java @@ -1,13 +1,11 @@ /******************************************************************************* - * Copyright (c) 2004, 2007 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2004, 2007 IBM Corporation and others. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ + * Contributors: IBM Corporation - initial API and implementation + ******************************************************************************/ package org.eclipse.osgi.internal.module; import java.util.*; @@ -100,12 +98,6 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver } } rewireBundles(); // Reconstruct wirings - ResolverBundle[] initBundles = (ResolverBundle[]) bundleMapping.values().toArray(new ResolverBundle[bundleMapping.size()]); - for (int i = 0; i < initBundles.length; i++) - // only initialize grouping constraint for resolved bundles; - // we add the constraints for unresolved bundles before we start a resolve opertation - if (initBundles[i].isResolved()) - groupingChecker.addInitialGroupingConstraints(initBundles[i]); setDebugOptions(); initialized = true; } @@ -179,10 +171,10 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver } private void rewireRequire(BundleConstraint req, ArrayList visited) { - if (req.getMatchingBundle() != null) + if (req.getSelectedSupplier() != null) return; ResolverBundle matchingBundle = (ResolverBundle) bundleMapping.get(req.getVersionConstraint().getSupplier()); - req.setMatchingBundle(matchingBundle); + req.addPossibleSupplier(matchingBundle); if (matchingBundle == null && !req.isOptional()) { System.err.println("Could not find matching bundle for " + req.getVersionConstraint()); //$NON-NLS-1$ // TODO log error!! @@ -193,7 +185,7 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver } private void rewireImport(ResolverImport imp, ArrayList visited) { - if (imp.isDynamic() || imp.getMatchingExport() != null) + if (imp.isDynamic() || imp.getSelectedSupplier() != null) return; // Re-wire 'imp' ResolverExport matchingExport = null; @@ -202,12 +194,12 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver Object[] matches = resolverExports.get(imp.getName()); for (int j = 0; j < matches.length; j++) { ResolverExport export = (ResolverExport) matches[j]; - if (export.getExporter() == exporter && imp.isSatisfiedBy(export)) { + if (export.getExporter() == exporter && importSupplier == export.getExportPackageDescription()) { matchingExport = export; break; } } - imp.setMatchingExport(matchingExport); + imp.addPossibleSupplier(matchingExport); // Check if we wired to a reprovided package (in which case the ResolverExport doesn't exist) if (matchingExport == null && exporter != null) { ResolverExport reprovidedExport = new ResolverExport(exporter, importSupplier); @@ -215,15 +207,15 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver exporter.addExport(reprovidedExport); resolverExports.put(reprovidedExport.getName(), reprovidedExport); } - imp.setMatchingExport(reprovidedExport); + imp.addPossibleSupplier(reprovidedExport); } // If we still have a null wire and it's not optional, then we have an error - if (imp.getMatchingExport() == null && !imp.isOptional()) { + if (imp.getSelectedSupplier() == null && !imp.isOptional()) { System.err.println("Could not find matching export for " + imp.getVersionConstraint()); //$NON-NLS-1$ // TODO log error!! } - if (imp.getMatchingExport() != null) { - rewireBundle(imp.getMatchingExport().getExporter(), visited); + if (imp.getSelectedSupplier() != null) { + rewireBundle(((ResolverExport) imp.getSelectedSupplier()).getExporter(), visited); } } @@ -408,23 +400,24 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver } } + // TODO this does not do proper uses constraint verification. private void resolveOptionalConstraints(ResolverBundle bundle) { BundleConstraint[] requires = bundle.getRequires(); ArrayList cycle = new ArrayList(); boolean resolvedOptional = false; for (int i = 0; i < requires.length; i++) - if (requires[i].isOptional() && requires[i].getMatchingBundle() == null) { + if (requires[i].isOptional() && requires[i].getSelectedSupplier() == null) { cycle.clear(); resolveRequire(requires[i], cycle); - if (requires[i].getMatchingBundle() != null) + if (requires[i].getSelectedSupplier() != null) resolvedOptional = true; } ResolverImport[] imports = bundle.getImportPackages(); for (int i = 0; i < imports.length; i++) - if (imports[i].isOptional() && imports[i].getMatchingExport() == null) { + if (imports[i].isOptional() && imports[i].getSelectedSupplier() == null) { cycle.clear(); - resolveImport(imports[i], true, cycle); - if (imports[i].getMatchingExport() != null) + resolveImport(imports[i], cycle); + if (imports[i].getSelectedSupplier() != null) resolvedOptional = true; } if (resolvedOptional) { @@ -451,19 +444,21 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver // we still want to call isResolvable here to populate any possible ResolverErrors for the bundle bundles[i].setResolvable(isResolvable(bundles[i].getBundle(), platformProperties, rejectedSingletons) || developmentMode); bundles[i].clearRefs(); - groupingChecker.removeAllExportConstraints(bundles[i]); } + resolveBundles0(bundles, platformProperties, rejectedSingletons); + if (DEBUG_WIRING) + printWirings(); + // set the resolved status of the bundles in the State + stateResolveBundles(bundles); + } + private void resolveBundles0(ResolverBundle[] bundles, Dictionary[] platformProperties, ArrayList rejectedSingletons) { // First attach all fragments to the matching hosts for (int i = 0; i < bundles.length; i++) attachFragment(bundles[i], rejectedSingletons); - // add initial grouping constraints after fragments have been attached - for (int i = 0; i < bundles.length; i++) - groupingChecker.addInitialGroupingConstraints(bundles[i]); // Lists of cyclic dependencies recording during resolving ArrayList cycle = new ArrayList(1); // start small - ArrayList resolvedBundles = new ArrayList(bundles.length); // Attempt to resolve all unresolved bundles for (int i = 0; i < bundles.length; i++) { if (DEBUG) @@ -473,21 +468,170 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver // Check for any bundles involved in a cycle. // if any bundles in the cycle are not resolved then we need to resolve the resolvable ones checkCycle(cycle); - if (bundles[i].isResolvable()) - resolvedBundles.add(bundles[i]); } - // Resolve all fragments that are still attached to at least one host. if (unresolvedBundles.size() > 0) { ResolverBundle[] unresolved = (ResolverBundle[]) unresolvedBundles.toArray(new ResolverBundle[unresolvedBundles.size()]); for (int i = 0; i < unresolved.length; i++) resolveFragment(unresolved[i]); } + checkUsesConstraints(bundles, platformProperties, rejectedSingletons); + } - if (DEBUG_WIRING) - printWirings(); - // set the resolved status of the bundles in the State - stateResolveBundles(bundles); + private void checkUsesConstraints(ResolverBundle[] bundles, Dictionary[] platformProperties, ArrayList rejectedSingletons) { + ResolverConstraint[] multipleSuppliers = getMultipleSuppliers(bundles); + ArrayList conflictingConstraints = findBestCombination(bundles, multipleSuppliers); + Set conflictedBundles = null; + if (conflictingConstraints != null) { + for (Iterator conflicts = conflictingConstraints.iterator(); conflicts.hasNext();) { + ResolverConstraint conflict = (ResolverConstraint) conflicts.next(); + if (conflict.isOptional()) { + conflict.clearPossibleSuppliers(); + continue; + } + conflictedBundles = new HashSet(conflictingConstraints.size()); + ResolverBundle conflictedBundle; + if (conflict.isFromFragment()) + conflictedBundle = (ResolverBundle) bundleMapping.get(conflict.getVersionConstraint().getBundle()); + else + conflictedBundle = conflict.getBundle(); + if (conflictedBundle != null) { + conflictedBundles.add(conflictedBundle); + int type = conflict instanceof ResolverImport ? ResolverError.IMPORT_PACKAGE_USES_CONFLICT : ResolverError.REQUIRE_BUNDLE_USES_CONFLICT; + state.addResolverError(conflictedBundle.getBundle(), type, conflict.getVersionConstraint().toString(), conflict.getVersionConstraint()); + conflictedBundle.setResolvable(false); + conflictedBundle.clearRefs(); + setBundleUnresolved(conflictedBundle, false, developmentMode); + } + } + if (conflictedBundles != null && conflictedBundles.size() > 0) { + ArrayList remainingUnresolved = new ArrayList(); + for (int i = 0; i < bundles.length; i++) { + if (!conflictedBundles.contains(bundles[i])) { + setBundleUnresolved(bundles[i], false, developmentMode); + remainingUnresolved.add(bundles[i]); + } + } + resolveBundles0((ResolverBundle[]) remainingUnresolved.toArray(new ResolverBundle[remainingUnresolved.size()]), platformProperties, rejectedSingletons); + } + } + } + + private ArrayList findBestCombination(ResolverBundle[] bundles, ResolverConstraint[] multipleSuppliers) { + int[] bestCombination = new int[multipleSuppliers.length]; + ArrayList conflictingBundles = null; + if (multipleSuppliers.length > 0) { + conflictingBundles = findBestCombination(bundles, multipleSuppliers, bestCombination); + for (int i = 0; i < bestCombination.length; i++) + multipleSuppliers[i].setSelectedSupplier(bestCombination[i]); + } else { + conflictingBundles = getConflicts(bundles); + } + // do not need to keep uses data in memory + groupingChecker.clear(); + return conflictingBundles; + } + + private void getCombination(ResolverConstraint[] multipleSuppliers, int[] combination) { + for (int i = 0; i < combination.length; i++) + combination[i] = multipleSuppliers[i].getSelectedSupplierIndex(); + } + + private ArrayList findBestCombination(ResolverBundle[] bundles, ResolverConstraint[] multipleSuppliers, int[] bestCombination) { + // first tryout all zeros + ArrayList bestConflicts = getConflicts(bundles); + if (bestConflicts == null) + return null; // the first selected have no conflicts; return without iterating over all combinations + // now iterate over every possible combination until either zero conflicts are found + // or we have run out of combinations + // if all combinations are tried then return the combination with the lowest number of conflicts + int bestConflictCount = getConflictCount(bestConflicts); + while (bestConflictCount != 0 && getNextCombination(multipleSuppliers)) { + ArrayList conflicts = getConflicts(bundles); + int conflictCount = getConflictCount(conflicts); + if (conflictCount < bestConflictCount) { + bestConflictCount = conflictCount; + bestConflicts = conflicts; + getCombination(multipleSuppliers, bestCombination); + } + } + return bestConflicts; + } + + private boolean getNextCombination(ResolverConstraint[] multipleSuppliers) { + if (multipleSuppliers[0].selectNextSupplier()) + return true; // the current slot has a next supplier + multipleSuppliers[0].setSelectedSupplier(0); // reset first slot + int current = 1; + while (current < multipleSuppliers.length) { + if (multipleSuppliers[current].selectNextSupplier()) + return true; + multipleSuppliers[current].setSelectedSupplier(0); // reset the current slot + current++; // move to the next slot + } + return false; + } + + // only count non-optional conflicts + private int getConflictCount(ArrayList conflicts) { + if (conflicts == null || conflicts.size() == 0) + return 0; + int result = 0; + for (Iterator iConflicts = conflicts.iterator(); iConflicts.hasNext();) + if (!((ResolverConstraint) iConflicts.next()).isOptional()) + result += 1; + return result; + } + + private ArrayList getConflicts(ResolverBundle[] bundles) { + groupingChecker.clear(); + ArrayList conflicts = null; + bundlesLoop: for (int i = 0; i < bundles.length; i++) { + BundleConstraint[] requires = bundles[i].getRequires(); + for (int j = 0; j < requires.length; j++) { + ResolverBundle selectedSupplier = (ResolverBundle) requires[j].getSelectedSupplier(); + ResolverExport conflict = selectedSupplier == null ? null : groupingChecker.isConsistent(bundles[i], selectedSupplier); + if (conflict != null) { + if (conflicts == null) + conflicts = new ArrayList(1); + conflicts.add(requires[j]); + // continue on for optonal conflicts because we don't count them + if (!requires[j].isOptional()) + continue bundlesLoop; + } + } + ResolverImport[] imports = bundles[i].getImportPackages(); + for (int j = 0; j < imports.length; j++) { + ResolverExport selectedSupplier = (ResolverExport) imports[j].getSelectedSupplier(); + ResolverExport conflict = selectedSupplier == null ? null : groupingChecker.isConsistent(bundles[i], selectedSupplier); + if (conflict != null) { + if (conflicts == null) + conflicts = new ArrayList(1); + conflicts.add(imports[j]); + // continue on for optional conflicts because we don't count them + if (!imports[j].isOptional()) + continue bundlesLoop; + } + } + } + return conflicts; + } + + // get a list of resolver constraints that have multiple suppliers + private ResolverConstraint[] getMultipleSuppliers(ResolverBundle[] bundles) { + ArrayList multipleSuppliers = new ArrayList(1); + for (int i = 0; i < bundles.length; i++) { + BundleConstraint[] requires = bundles[i].getRequires(); + for (int j = 0; j < requires.length; j++) + if (requires[j].getNumPossibleSuppliers() > 1) + multipleSuppliers.add(requires[j]); + ResolverImport[] imports = bundles[i].getImportPackages(); + for (int j = 0; j < imports.length; j++) { + if (imports[j].getNumPossibleSuppliers() > 1) + multipleSuppliers.add(imports[j]); + } + } + return (ResolverConstraint[]) multipleSuppliers.toArray(new ResolverConstraint[multipleSuppliers.size()]); } private void checkCycle(ArrayList cycle) { @@ -496,37 +640,37 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver return; for (int i = cycleSize - 1; i >= 0; i--) { ResolverBundle cycleBundle = (ResolverBundle) cycle.get(i); - // clear grouping (uses) constraints so we can do proper constaint checking now that the cycle is resolved - groupingChecker.removeAllExportConstraints(cycleBundle); - groupingChecker.addInitialGroupingConstraints(cycleBundle); if (!cycleBundle.isResolvable()) cycle.remove(i); // remove this from the list of bundles that need reresolved - } - boolean reresolveCycle = cycle.size() != cycleSize || !isCycleConsistent(cycle); //we removed an unresolvable bundle; must reresolve remaining cycle - for (int i = 0; i < cycle.size(); i++) { - ResolverBundle rb = (ResolverBundle) cycle.get(0); // Check that we haven't wired to any dropped exports - ResolverImport[] imports = rb.getImportPackages(); - for (int j = 0; j < imports.length; j++) + ResolverImport[] imports = cycleBundle.getImportPackages(); + for (int j = 0; j < imports.length; j++) { // check for dropped exports - if (imports[j].getMatchingExport() != null && imports[j].getMatchingExport().isDropped()) { - imports[j].addUnresolvableWiring(imports[j].getMatchingExport().getExporter()); - reresolveCycle = true; + while (imports[j].getSelectedSupplier() != null) { + ResolverExport importSupplier = (ResolverExport) imports[j].getSelectedSupplier(); + if (importSupplier.isDropped()) + imports[j].selectNextSupplier(); + else + break; } + if (!imports[j].isDynamic() && !imports[j].isOptional() && imports[j].getSelectedSupplier() == null) { + cycleBundle.setResolvable(false); + cycleBundle.clearRefs(); + state.addResolverError(imports[j].getVersionConstraint().getBundle(), ResolverError.MISSING_IMPORT_PACKAGE, imports[j].getVersionConstraint().toString(), imports[j].getVersionConstraint()); + cycle.remove(i); + } + } } + boolean reresolveCycle = cycle.size() != cycleSize; //we removed an unresolvable bundle; must reresolve remaining cycle if (reresolveCycle) { for (int i = 0; i < cycle.size(); i++) { ResolverBundle cycleBundle = (ResolverBundle) cycle.get(i); - groupingChecker.removeAllExportConstraints(cycleBundle); - groupingChecker.addInitialGroupingConstraints(cycleBundle); - cycleBundle.clearWires(false); + cycleBundle.clearWires(); cycleBundle.clearRefs(); } - groupingChecker.setCheckCycles(true); // need to do the expensive cycle checks now ArrayList innerCycle = new ArrayList(cycle.size()); for (int i = 0; i < cycle.size(); i++) resolveBundle((ResolverBundle) cycle.get(i), innerCycle); - groupingChecker.setCheckCycles(false); // disable the expensive cycle checks checkCycle(innerCycle); } else { for (int i = 0; i < cycle.size(); i++) { @@ -537,23 +681,6 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver } } - // checks all the uses constraints of a cycle to make sure they - // are consistent now that the cycle has been resolved. - private boolean isCycleConsistent(ArrayList cycle) { - for (Iterator iter = cycle.iterator(); iter.hasNext();) { - ResolverBundle bundle = (ResolverBundle) iter.next(); - BundleConstraint[] requires = bundle.getRequires(); - for (int i = 0; i < requires.length; i++) - if (requires[i].getMatchingBundle() != null && groupingChecker.isConsistent(requires[i], requires[i].getMatchingBundle()) != null) - return false; - ResolverImport[] imports = bundle.getImportPackages(); - for (int i = 0; i < imports.length; i++) - if (imports[i].getMatchingExport() != null && groupingChecker.isConsistent(imports[i], imports[i].getMatchingExport()) != null) - return false; - } - return true; - } - private boolean selectSingletons(ResolverBundle[] bundles, ArrayList rejectedSingletons) { if (developmentMode) return false; // do no want to unresolve singletons in development mode @@ -592,11 +719,9 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver private void resolveFragment(ResolverBundle fragment) { if (!fragment.isFragment()) return; - if (fragment.getHost().foundMatchingBundles()) { - stateResolveFragConstraints(fragment); + if (fragment.getHost().getNumPossibleSuppliers() > 0) if (!developmentMode || state.getResolverErrors(fragment.getBundle()).length == 0) setBundleResolved(fragment); - } } // This method will attempt to resolve the supplied bundle and any bundles that it is dependent on @@ -615,7 +740,7 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver return true; } else if (bundle.getState() == ResolverBundle.UNRESOLVED) { // 'bundle' is UNRESOLVED so move to RESOLVING - bundle.clearWires(true); + bundle.clearWires(); setBundleResolving(bundle); } @@ -670,7 +795,7 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver ResolverImport[] imports = bundle.getImportPackages(); for (int i = 0; i < imports.length; i++) { // Only resolve non-dynamic imports here - if (!imports[i].isDynamic() && !resolveImport(imports[i], true, cycle)) { + if (!imports[i].isDynamic() && !resolveImport(imports[i], cycle)) { if (DEBUG || DEBUG_IMPORTS) ResolverImpl.log("** IMPORT " + imports[i].getName() + "[" + imports[i].getBundleDescription() + "] failed to resolve"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ // If the import has failed to resolve and it is from a fragment, then remove the fragment from the host @@ -711,8 +836,6 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver if (bundle.getState() == ResolverBundle.UNRESOLVED) bundle.setResolvable(false); // Set it to unresolvable so we don't attempt to resolve it again in this round - // tell the state what we resolved the constraints to - stateResolveConstraints(bundle); return bundle.getState() != ResolverBundle.UNRESOLVED; } @@ -758,21 +881,22 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver result = true; // Wired to ourselves continue; } - ResolverBundle[] capabilityHosts = capability.isFromFragment() ? capability.getResolverBundle().getHost().getMatchingBundles() : new ResolverBundle[] {capability.getResolverBundle()}; + VersionSupplier[] capabilityHosts = capability.isFromFragment() ? capability.getResolverBundle().getHost().getPossibleSuppliers() : new ResolverBundle[] {capability.getResolverBundle()}; boolean foundResolvedMatch = false; for (int j = 0; capabilityHosts != null && j < capabilityHosts.length; j++) { - if (capabilityHosts[j] == constraint.getBundle()) { + ResolverBundle capabilitySupplier = (ResolverBundle) capabilityHosts[j]; + if (capabilitySupplier == constraint.getBundle()) { // the capability is from a fragment attached to this host do not recursively resolve the host again foundResolvedMatch = true; continue; } // if in dev mode then allow a constraint to resolve to an unresolved bundle - if (capabilityHosts[j].getState() == ResolverBundle.RESOLVED || (resolveBundle(capabilityHosts[j], cycle) || developmentMode)) { - foundResolvedMatch |= !capability.isFromFragment() ? true : capability.getResolverBundle().getHost().getMatchingBundles() != null; + if (capabilitySupplier.getState() == ResolverBundle.RESOLVED || (resolveBundle(capabilitySupplier, cycle) || developmentMode)) { + foundResolvedMatch |= !capability.isFromFragment() ? true : capability.getResolverBundle().getHost().getPossibleSuppliers() != null; // Check cyclic dependencies - if (capabilityHosts[j].getState() == ResolverBundle.RESOLVING) - if (!cycle.contains(capabilityHosts[j])) - cycle.add(capabilityHosts[j]); + if (capabilitySupplier.getState() == ResolverBundle.RESOLVING) + if (!cycle.contains(capabilitySupplier)) + cycle.add(capabilitySupplier); } } if (!foundResolvedMatch) { @@ -791,14 +915,13 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver private boolean resolveRequire(BundleConstraint req, ArrayList cycle) { if (DEBUG_REQUIRES) ResolverImpl.log("Trying to resolve: " + req.getBundle() + ", " + req.getVersionConstraint()); //$NON-NLS-1$ //$NON-NLS-2$ - if (req.getMatchingBundle() != null) { + if (req.getSelectedSupplier() != null) { // Check for unrecorded cyclic dependency - if (req.getMatchingBundle().getState() == ResolverBundle.RESOLVING) - if (!cycle.contains(req.getBundle())) { - cycle.add(req.getBundle()); - if (DEBUG_CYCLES) - ResolverImpl.log("require-bundle cycle: " + req.getBundle() + " -> " + req.getMatchingBundle()); //$NON-NLS-1$ //$NON-NLS-2$ - } + if (!cycle.contains(req.getBundle())) { + cycle.add(req.getBundle()); + if (DEBUG_CYCLES) + ResolverImpl.log("require-bundle cycle: " + req.getBundle() + " -> " + req.getSelectedSupplier()); //$NON-NLS-1$ //$NON-NLS-2$ + } if (DEBUG_REQUIRES) ResolverImpl.log(" - already wired"); //$NON-NLS-1$ return true; // Already wired (due to grouping dependencies) so just return @@ -812,29 +935,28 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver // Check if export matches if (req.isSatisfiedBy(bundle)) { bundle.addRef(req.getBundle()); - if (result) - continue; - req.setMatchingBundle(bundle); // Wire to the bundle - if (req.getBundle() == bundle) { - result = true; // Wired to ourselves - continue; - } - // if in dev mode then allow a constraint to resolve to an unresolved bundle - if (bundle.getState() != ResolverBundle.RESOLVED && !resolveBundle(bundle, cycle) && !developmentMode) { - req.setMatchingBundle(null); - continue; // Bundle hasn't resolved + // first add the possible supplier; this is done before resolving the supplier bundle to prevent endless cycle loops. + req.addPossibleSupplier(bundle); + if (req.getBundle() != bundle) { + // if in dev mode then allow a constraint to resolve to an unresolved bundle + if (bundle.getState() != ResolverBundle.RESOLVED && !resolveBundle(bundle, cycle) && !developmentMode) { + req.removePossibleSupplier(bundle); + continue; // Bundle hasn't resolved + } } // Check cyclic dependencies - if (bundle.getState() == ResolverBundle.RESOLVING) - // If the bundle is RESOLVING, we have a cyclic dependency - if (!cycle.contains(req.getBundle())) { - cycle.add(req.getBundle()); - if (DEBUG_CYCLES) - ResolverImpl.log("require-bundle cycle: " + req.getBundle() + " -> " + req.getMatchingBundle()); //$NON-NLS-1$ //$NON-NLS-2$ - } + if (req.getBundle() != bundle) { + if (bundle.getState() == ResolverBundle.RESOLVING) + // If the bundle is RESOLVING, we have a cyclic dependency + if (!cycle.contains(req.getBundle())) { + cycle.add(req.getBundle()); + if (DEBUG_CYCLES) + ResolverImpl.log("require-bundle cycle: " + req.getBundle() + " -> " + req.getSelectedSupplier()); //$NON-NLS-1$ //$NON-NLS-2$ + } + } if (DEBUG_REQUIRES) ResolverImpl.log("Found match: " + bundle.getBundle() + ". Wiring"); //$NON-NLS-1$ //$NON-NLS-2$ - result = checkRequiresConstraints(req, req.getMatchingBundle()); + result = true; } } if (result || req.isOptional()) @@ -843,27 +965,17 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver return false; } - private boolean checkRequiresConstraints(BundleConstraint req, ResolverBundle bundle) { - if (groupingChecker.isConsistent(req, bundle) != null) { - req.setMatchingBundle(null); - state.addResolverError(req.getBundleDescription(), ResolverError.REQUIRE_BUNDLE_USES_CONFLICT, bundle.getBundle().toString(), req.getVersionConstraint()); - return req.isOptional(); - } - return true; - } - // Resolve the supplied import. Returns true if the import can be resolved, false otherwise - private boolean resolveImport(ResolverImport imp, boolean checkReexportsFromRequires, ArrayList cycle) { + private boolean resolveImport(ResolverImport imp, ArrayList cycle) { if (DEBUG_IMPORTS) ResolverImpl.log("Trying to resolve: " + imp.getBundle() + ", " + imp.getName()); //$NON-NLS-1$ //$NON-NLS-2$ - if (imp.getMatchingExport() != null) { + if (imp.getSelectedSupplier() != null) { // Check for unrecorded cyclic dependency - if (imp.getMatchingExport().getExporter().getState() == ResolverBundle.RESOLVING) - if (!cycle.contains(imp.getBundle())) { - cycle.add(imp.getBundle()); - if (DEBUG_CYCLES) - ResolverImpl.log("import-package cycle: " + imp.getBundle() + " -> " + imp.getMatchingExport() + " from " + imp.getMatchingExport().getBundle()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - } + if (!cycle.contains(imp.getBundle())) { + cycle.add(imp.getBundle()); + if (DEBUG_CYCLES) + ResolverImpl.log("import-package cycle: " + imp.getBundle() + " -> " + imp.getSelectedSupplier() + " from " + imp.getSelectedSupplier().getBundle()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } if (DEBUG_IMPORTS) ResolverImpl.log(" - already wired"); //$NON-NLS-1$ return true; // Already wired (due to grouping dependencies) so just return @@ -875,16 +987,15 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver if (DEBUG_IMPORTS) ResolverImpl.log("CHECKING: " + export.getExporter().getBundle() + ", " + export.getName()); //$NON-NLS-1$ //$NON-NLS-2$ // Check if export matches - if (imp.isSatisfiedBy(export) && imp.isNotAnUnresolvableWiring(export)) { + if (imp.isSatisfiedBy(export)) { int originalState = export.getExporter().getState(); if (imp.isDynamic() && originalState != ResolverBundle.RESOLVED) continue; // Must not attempt to resolve an exporter when dynamic if (imp.getBundle() == export.getExporter() && !export.getExportPackageDescription().isRoot()) continue; // Can't wire to our own re-export export.getExporter().addRef(imp.getBundle()); - if (result) - continue; - imp.setMatchingExport(export); // Wire the import to the export + // first add the possible supplier; this is done before resolving the supplier bundle to prevent endless cycle loops. + imp.addPossibleSupplier(export); ResolverExport[] importerExps = null; if (imp.getBundle() != export.getExporter()) { // Save the exports of this package from the importer in case we need to add them back @@ -897,54 +1008,40 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver } // if in dev mode then allow a constraint to resolve to an unresolved bundle if ((originalState != ResolverBundle.RESOLVED && !resolveBundle(export.getExporter(), cycle) && !developmentMode) || export.isDropped()) { - if (imp.getMatchingExport() != null && imp.getMatchingExport() != export) // has been resolved to some other export recursively - return true; + // remove the possible supplier + imp.removePossibleSupplier(export); // add back the exports of this package from the importer for (int j = 0; j < importerExps.length; j++) resolverExports.put(importerExps[j].getName(), importerExps[j]); - imp.setMatchingExport(null); continue; // Bundle hasn't resolved || export has not been selected and is unavailable } - } - // If the importer has become unresolvable then stop here - if (!imp.getBundle().isResolvable()) - return false; - // Check grouping dependencies - if (checkImportConstraints(imp, imp.getMatchingExport(), cycle, importerExps) && imp.getMatchingExport() != null) { - // Record any cyclic dependencies - if (export != imp.getMatchingExport()) - export = imp.getMatchingExport(); - if (imp.getBundle() != export.getExporter()) - if (export.getExporter().getState() == ResolverBundle.RESOLVING) { - // If the exporter is RESOLVING, we have a cyclic dependency - if (!cycle.contains(imp.getBundle())) { - cycle.add(imp.getBundle()); - if (DEBUG_CYCLES) - ResolverImpl.log("import-package cycle: " + imp.getBundle() + " -> " + imp.getMatchingExport() + " from " + imp.getMatchingExport().getBundle()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - } + } else if (export.isDropped()) + continue; // we already found a possible import that satisifies us; our export is dropped + + // Record any cyclic dependencies + if (imp.getBundle() != export.getExporter()) + if (export.getExporter().getState() == ResolverBundle.RESOLVING) { + // If the exporter is RESOLVING, we have a cyclic dependency + if (!cycle.contains(imp.getBundle())) { + cycle.add(imp.getBundle()); + if (DEBUG_CYCLES) + ResolverImpl.log("import-package cycle: " + imp.getBundle() + " -> " + imp.getSelectedSupplier() + " from " + imp.getSelectedSupplier().getBundle()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } - if (DEBUG_IMPORTS) - ResolverImpl.log("Found match: " + export.getExporter() + ". Wiring " + imp.getBundle() + ":" + imp.getName()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - result = true; - } else if (!imp.getBundle().isResolvable()) { - // If grouping has caused recursive calls to resolveImport, and the grouping has failed - // then we need to catch that here, so we don't continue trying to wire here - return false; - } - if (!result && imp.getMatchingExport() != null && imp.getMatchingExport() != export) - return true; // Grouping has changed the wiring, so return here + } + if (DEBUG_IMPORTS) + ResolverImpl.log("Found match: " + export.getExporter() + ". Wiring " + imp.getBundle() + ":" + imp.getName()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + result = true; } } + if (result) return true; - if (checkReexportsFromRequires && resolveImportReprovide(imp, cycle)) - return true; // A reprovide satisfies imp + if (resolveImportReprovide(imp, cycle)) + return true; if (imp.isOptional()) return true; // If the import is optional then just return true - return false; } - // Check if the import can be resolved to a re-exported package (has no export object to match to) private boolean resolveImportReprovide(ResolverImport imp, ArrayList cycle) { String bsn = ((ImportPackageSpecification) imp.getVersionConstraint()).getBundleSymbolicName(); @@ -972,9 +1069,9 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver if (!((BundleSpecification) requires[i].getVersionConstraint()).isExported()) continue; // Skip require if it doesn't re-export the packages // Check exports to see if we've found the root - if (requires[i].getMatchingBundle() == null) + if (requires[i].getSelectedSupplier() == null) continue; - ResolverExport[] exports = requires[i].getMatchingBundle().getExports(imp.getName()); + ResolverExport[] exports = ((ResolverBundle) requires[i].getSelectedSupplier()).getExports(imp.getName()); for (int j = 0; j < exports.length; j++) { Map directives = exports[j].getExportPackageDescription().getDirectives(); directives.remove(Constants.USES_DIRECTIVE); @@ -987,56 +1084,17 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver reexporter.addExport(re); resolverExports.put(re.getName(), re); // Resolve import - if (resolveImport(imp, false, cycle)) - return true; + imp.addPossibleSupplier(re); + return true; } } // Check requires of matching bundle (recurse down the chain) - if (resolveImportReprovide0(imp, reexporter, requires[i].getMatchingBundle(), cycle, visited)) + if (resolveImportReprovide0(imp, reexporter, (ResolverBundle) requires[i].getSelectedSupplier(), cycle, visited)) return true; } return false; } - // This method checks and resolves (if possible) grouping dependencies - // Returns true, if the dependencies can be resolved, false otherwise - private boolean checkImportConstraints(ResolverImport imp, ResolverExport exp, ArrayList cycle, ResolverExport[] importerExps) { - if (DEBUG_GROUPING) - ResolverImpl.log(" Checking grouping for " + imp.getBundle() + ":" + imp.getName() + " -> " + exp.getExporter() + ":" + exp.getName()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ - ResolverBundle importer = imp.getBundle(); - ResolverExport clash = groupingChecker.isConsistent(imp, exp); - if (clash == null) - return true; - if (DEBUG_GROUPING) - ResolverImpl.log(" * grouping clash with " + clash.getExporter() + ":" + clash.getName()); //$NON-NLS-1$ //$NON-NLS-2$ - // Try to rewire imp - imp.addUnresolvableWiring(exp.getExporter()); - imp.setMatchingExport(null); - if (resolveImport(imp, false, cycle)) - return true; - if (imp.isDynamic()) - return false; - // Rewiring of imp has failed so try to rewire clashing import - imp.clearUnresolvableWirings(); - imp.setMatchingExport(exp); - ResolverImport[] imports = importer.getImportPackages(); - for (int i = 0; i < imports.length; i++) { - if (imports[i].getMatchingExport() != null && imports[i].getMatchingExport().getName().equals(clash.getName())) { - imports[i].addUnresolvableWiring(imports[i].getMatchingExport().getExporter()); - imports[i].setMatchingExport(null); // clear the conflicting wire - // If the clashing import package was also exported then - // we need to put the export back into resolverExports - if (importerExps != null) - resolverExports.put(importerExps); - } - } - // Try to re-resolve the bundle - if (resolveBundle(importer, cycle)) - return true; - state.addResolverError(imp.getVersionConstraint().getBundle(), ResolverError.IMPORT_PACKAGE_USES_CONFLICT, imp.getVersionConstraint().toString(), imp.getVersionConstraint()); - return false; - } - // Move a bundle to UNRESOLVED private void setBundleUnresolved(ResolverBundle bundle, boolean removed, boolean isDevMode) { if (bundle.getState() == ResolverBundle.UNRESOLVED) @@ -1074,21 +1132,22 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver // Resolves the bundles in the State private void stateResolveBundles(ResolverBundle[] resolvedBundles) { - for (int i = 0; i < resolvedBundles.length; i++) + for (int i = 0; i < resolvedBundles.length; i++) { if (!resolvedBundles[i].getBundle().isResolved()) stateResolveBundle(resolvedBundles[i]); + } } private void stateResolveConstraints(ResolverBundle rb) { ResolverImport[] imports = rb.getImportPackages(); for (int i = 0; i < imports.length; i++) { - ResolverExport export = imports[i].getMatchingExport(); + ResolverExport export = (ResolverExport) imports[i].getSelectedSupplier(); BaseDescription supplier = export == null ? null : export.getExportPackageDescription(); state.resolveConstraint(imports[i].getVersionConstraint(), supplier); } BundleConstraint[] requires = rb.getRequires(); for (int i = 0; i < requires.length; i++) { - ResolverBundle bundle = requires[i].getMatchingBundle(); + ResolverBundle bundle = (ResolverBundle) requires[i].getSelectedSupplier(); BaseDescription supplier = bundle == null ? null : bundle.getBundle(); state.resolveConstraint(requires[i].getVersionConstraint(), supplier); } @@ -1104,18 +1163,18 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver } private void stateResolveFragConstraints(ResolverBundle rb) { - ResolverBundle host = rb.getHost().getMatchingBundle(); + ResolverBundle host = (ResolverBundle) rb.getHost().getSelectedSupplier(); ImportPackageSpecification[] imports = rb.getBundle().getImportPackages(); for (int i = 0; i < imports.length; i++) { ResolverImport hostImport = host.getImport(imports[i].getName()); - ResolverExport export = hostImport == null ? null : hostImport.getMatchingExport(); + ResolverExport export = (ResolverExport) (hostImport == null ? null : hostImport.getSelectedSupplier()); BaseDescription supplier = export == null ? null : export.getExportPackageDescription(); state.resolveConstraint(imports[i], supplier); } BundleSpecification[] requires = rb.getBundle().getRequiredBundles(); for (int i = 0; i < requires.length; i++) { BundleConstraint hostRequire = host.getRequire(requires[i].getName()); - ResolverBundle bundle = hostRequire == null ? null : hostRequire.getMatchingBundle(); + ResolverBundle bundle = (ResolverBundle) (hostRequire == null ? null : hostRequire.getSelectedSupplier()); BaseDescription supplier = bundle == null ? null : bundle.getBundle(); state.resolveConstraint(requires[i], supplier); } @@ -1125,6 +1184,10 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver // if in dev mode then we want to tell the state about the constraints we were able to resolve if (!rb.isResolved() && !developmentMode) return; + if (rb.isFragment()) + stateResolveFragConstraints(rb); + else + stateResolveConstraints(rb); // Gather selected exports ResolverExport[] exports = rb.getSelectedExports(); ArrayList selectedExports = new ArrayList(exports.length); @@ -1136,31 +1199,29 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver // Gather exports that have been wired to ResolverImport[] imports = rb.getImportPackages(); ArrayList exportsWiredTo = new ArrayList(imports.length); - for (int i = 0; i < imports.length; i++) { - if (imports[i].getMatchingExport() != null) { - exportsWiredTo.add(imports[i].getMatchingExport().getExportPackageDescription()); - } - } + for (int i = 0; i < imports.length; i++) + if (imports[i].getSelectedSupplier() != null) + exportsWiredTo.add(imports[i].getSelectedSupplier().getBaseDescription()); ExportPackageDescription[] exportsWiredToArray = (ExportPackageDescription[]) exportsWiredTo.toArray(new ExportPackageDescription[exportsWiredTo.size()]); // Gather bundles that have been wired to BundleConstraint[] requires = rb.getRequires(); ArrayList bundlesWiredTo = new ArrayList(requires.length); for (int i = 0; i < requires.length; i++) - if (requires[i].getMatchingBundle() != null) - bundlesWiredTo.add(requires[i].getMatchingBundle().getBundle()); + if (requires[i].getSelectedSupplier() != null) + bundlesWiredTo.add(requires[i].getSelectedSupplier().getBaseDescription()); BundleDescription[] bundlesWiredToArray = (BundleDescription[]) bundlesWiredTo.toArray(new BundleDescription[bundlesWiredTo.size()]); BundleDescription[] hostBundles = null; if (rb.isFragment()) { - ResolverBundle[] matchingBundles = rb.getHost().getMatchingBundles(); + VersionSupplier[] matchingBundles = rb.getHost().getPossibleSuppliers(); if (matchingBundles != null && matchingBundles.length > 0) { hostBundles = new BundleDescription[matchingBundles.length]; for (int i = 0; i < matchingBundles.length; i++) { hostBundles[i] = matchingBundles[i].getBundle(); if (rb.isNewFragmentExports() && hostBundles[i].isResolved()) { // update the host's set of selected exports - ResolverExport[] hostExports = matchingBundles[i].getSelectedExports(); + ResolverExport[] hostExports = ((ResolverBundle) matchingBundles[i]).getSelectedExports(); ExportPackageDescription[] hostExportsArray = new ExportPackageDescription[hostExports.length]; for (int j = 0; j < hostExports.length; j++) hostExportsArray[j] = hostExports[j].getExportPackageDescription(); @@ -1203,16 +1264,30 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver // Resolve the import if (requestedPackage.equals(resolverImports[j].getName())) { found = true; - if (resolveImport(resolverImports[j], true, new ArrayList())) { - // If the import resolved then return it's matching export + // populate the grouping checker with current imports + groupingChecker.populateRoots(resolverImports[j].getBundle()); + if (resolveImport(resolverImports[j], new ArrayList())) { + found = false; + while (!found && resolverImports[j].getSelectedSupplier() != null) { + if (groupingChecker.isDynamicConsistent(resolverImports[j].getBundle(), (ResolverExport) resolverImports[j].getSelectedSupplier()) != null) + resolverImports[j].selectNextSupplier(); // not consistent; try the next + else + found = true; // found a valid wire + } resolverImports[j].setName(null); + if (!found) { + // not found or there was a conflict; reset the suppliers and return null + resolverImports[j].setPossibleSuppliers(null); + return null; + } + // If the import resolved then return it's matching export if (DEBUG_IMPORTS) - ResolverImpl.log("Resolved dynamic import: " + rb + ":" + resolverImports[j].getName() + " -> " + resolverImports[j].getMatchingExport().getExporter() + ":" + requestedPackage); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ - ExportPackageDescription matchingExport = resolverImports[j].getMatchingExport().getExportPackageDescription(); + ResolverImpl.log("Resolved dynamic import: " + rb + ":" + resolverImports[j].getName() + " -> " + ((ResolverExport) resolverImports[j].getSelectedSupplier()).getExporter() + ":" + requestedPackage); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + ExportPackageDescription matchingExport = ((ResolverExport) resolverImports[j].getSelectedSupplier()).getExportPackageDescription(); // If it is a wildcard import then clear the wire, so other // exported packages can be found for it if (importName.endsWith("*")) //$NON-NLS-1$ - resolverImports[j].setMatchingExport(null); + resolverImports[j].setPossibleSuppliers(null); return matchingExport; } } @@ -1225,8 +1300,15 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver directives.put(Constants.RESOLUTION_DIRECTIVE, ImportPackageSpecification.RESOLUTION_DYNAMIC); ImportPackageSpecification packageSpec = state.getFactory().createImportPackageSpecification(requestedPackage, null, null, null, directives, null, importingBundle); ResolverImport newImport = new ResolverImport(rb, packageSpec); - if (resolveImport(newImport, true, new ArrayList())) - return newImport.getMatchingExport().getExportPackageDescription(); + if (resolveImport(newImport, new ArrayList())) { + while (newImport.getSelectedSupplier() != null) { + if (groupingChecker.isDynamicConsistent(rb, (ResolverExport) newImport.getSelectedSupplier()) != null) + newImport.selectNextSupplier(); + else + break; + } + return ((ResolverExport) newImport.getSelectedSupplier()).getExportPackageDescription(); + } } if (DEBUG || DEBUG_IMPORTS) ResolverImpl.log("Failed to resolve dynamic import: " + requestedPackage); //$NON-NLS-1$ @@ -1266,7 +1348,7 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver if (!pending) { bundleMapping.remove(bundle); - groupingChecker.removeAllExportConstraints(rb); + groupingChecker.remove(rb); } if (!pending || !bundle.isResolved()) { resolverExports.remove(rb.getExportPackages()); @@ -1289,7 +1371,7 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver resolverBundles.remove(re); resolverGenerics.remove(re.getGenericCapabilities()); bundleMapping.remove(removedBundles[i]); - groupingChecker.removeAllExportConstraints(re); + groupingChecker.remove(re); // the bundle is removed if (removedBundles[i] == bundle.getBundle()) removed = true; @@ -1365,17 +1447,17 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver ResolverImpl.log(" (r) no requires"); //$NON-NLS-1$ } else { for (int i = 0; i < requireBundles.length; i++) { - if (requireBundles[i].getMatchingBundle() == null) { + if (requireBundles[i].getSelectedSupplier() == null) { ResolverImpl.log(" (r) " + rb.getBundle() + " -> NULL!!!"); //$NON-NLS-1$ //$NON-NLS-2$ } else { - ResolverImpl.log(" (r) " + rb.getBundle() + " -> " + requireBundles[i].getMatchingBundle()); //$NON-NLS-1$ //$NON-NLS-2$ + ResolverImpl.log(" (r) " + rb.getBundle() + " -> " + requireBundles[i].getSelectedSupplier()); //$NON-NLS-1$ //$NON-NLS-2$ } } } // Hosts BundleConstraint hostSpec = rb.getHost(); if (hostSpec != null) { - ResolverBundle[] hosts = hostSpec.getMatchingBundles(); + VersionSupplier[] hosts = hostSpec.getPossibleSuppliers(); if (hosts != null) for (int i = 0; i < hosts.length; i++) { ResolverImpl.log(" (h) " + rb.getBundle() + " -> " + hosts[i].getBundle()); //$NON-NLS-1$ //$NON-NLS-2$ @@ -1388,15 +1470,15 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver continue; } for (int i = 0; i < imports.length; i++) { - if (imports[i].isDynamic() && imports[i].getMatchingExport() == null) { + if (imports[i].isDynamic() && imports[i].getSelectedSupplier() == null) { ResolverImpl.log(" (w) " + imports[i].getBundle() + ":" + imports[i].getName() + " -> DYNAMIC"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - } else if (imports[i].isOptional() && imports[i].getMatchingExport() == null) { + } else if (imports[i].isOptional() && imports[i].getSelectedSupplier() == null) { ResolverImpl.log(" (w) " + imports[i].getBundle() + ":" + imports[i].getName() + " -> OPTIONAL (could not be wired)"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - } else if (imports[i].getMatchingExport() == null) { + } else if (imports[i].getSelectedSupplier() == null) { ResolverImpl.log(" (w) " + imports[i].getBundle() + ":" + imports[i].getName() + " -> NULL!!!"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } else { ResolverImpl.log(" (w) " + imports[i].getBundle() + ":" + imports[i].getName() + " -> " + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - imports[i].getMatchingExport().getExporter() + ":" + imports[i].getMatchingExport().getName()); //$NON-NLS-1$ + ((ResolverExport) imports[i].getSelectedSupplier()).getExporter() + ":" + imports[i].getSelectedSupplier().getName()); //$NON-NLS-1$ } } } diff --git a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverImport.java b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverImport.java index 1e9919f78..848841ba6 100644 --- a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverImport.java +++ b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverImport.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2004, 2005 IBM Corporation and others. + * Copyright (c) 2004, 2007 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,7 +10,6 @@ *******************************************************************************/ package org.eclipse.osgi.internal.module; -import java.util.ArrayList; import org.eclipse.osgi.service.resolver.ImportPackageSpecification; import org.osgi.framework.Constants; @@ -18,9 +17,6 @@ import org.osgi.framework.Constants; * A companion to ImportPackageSpecification from the state used while resolving */ public class ResolverImport extends ResolverConstraint { - private ResolverExport matchingExport; - // Wires we know will cause the module to become unresolvable (due to grouping dependencies) - private ArrayList unresolvableWirings = new ArrayList(); // only used for dynamic imports private String name; @@ -36,31 +32,6 @@ public class ResolverImport extends ResolverConstraint { return ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(((ImportPackageSpecification) constraint).getDirective(Constants.RESOLUTION_DIRECTIVE)); } - ResolverExport getMatchingExport() { - return matchingExport; - } - - void setMatchingExport(ResolverExport matchingExport) { - this.matchingExport = matchingExport; - } - - // Records an unresolvable wiring for this import (grouping dependencies) - void addUnresolvableWiring(ResolverBundle exporter) { - unresolvableWirings.add(exporter); - } - - // Clear the list of all the unresovable wirings. This is called when we move - // the module to UNRESOLVED so we can start from scratch if we ever try again - void clearUnresolvableWirings() { - unresolvableWirings = new ArrayList(); - } - - // Returns true if the supplied export has not been recorded as - // an unresolvable wiring for this import - boolean isNotAnUnresolvableWiring(ResolverExport exp) { - return !unresolvableWirings.contains(exp.getExporter()); - } - public String getName() { if (name != null) return name; // return the required package set for a dynamic import |