diff options
author | Thomas Watson | 2012-08-17 16:09:44 +0000 |
---|---|---|
committer | Thomas Watson | 2012-10-02 18:54:41 +0000 |
commit | 31fa1aac8b141ce36d6477d4107209e543269e86 (patch) | |
tree | 37b142dd2a987a3abcc4df1cda5d95f3655708ec /bundles/org.eclipse.osgi.compatibility.state | |
parent | 4f277ed781bfb41298899656078984f65238012d (diff) | |
download | rt.equinox.framework-31fa1aac8b141ce36d6477d4107209e543269e86.tar.gz rt.equinox.framework-31fa1aac8b141ce36d6477d4107209e543269e86.tar.xz rt.equinox.framework-31fa1aac8b141ce36d6477d4107209e543269e86.zip |
initial checking of compatibility state fragment
Diffstat (limited to 'bundles/org.eclipse.osgi.compatibility.state')
46 files changed, 13195 insertions, 0 deletions
diff --git a/bundles/org.eclipse.osgi.compatibility.state/.classpath b/bundles/org.eclipse.osgi.compatibility.state/.classpath new file mode 100644 index 000000000..ad32c83a7 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/bundles/org.eclipse.osgi.compatibility.state/.gitignore b/bundles/org.eclipse.osgi.compatibility.state/.gitignore new file mode 100644 index 000000000..07a846bd7 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/.gitignore @@ -0,0 +1,2 @@ +/bin/ +.DS_Store diff --git a/bundles/org.eclipse.osgi.compatibility.state/.project b/bundles/org.eclipse.osgi.compatibility.state/.project new file mode 100644 index 000000000..50487e24b --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.osgi.compatibility.state</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/bundles/org.eclipse.osgi.compatibility.state/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.osgi.compatibility.state/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..c537b6306 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/bundles/org.eclipse.osgi.compatibility.state/.settings/org.eclipse.pde.core.prefs b/bundles/org.eclipse.osgi.compatibility.state/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..f29e940a0 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/bundles/org.eclipse.osgi.compatibility.state/META-INF/MANIFEST.MF b/bundles/org.eclipse.osgi.compatibility.state/META-INF/MANIFEST.MF new file mode 100644 index 000000000..990af6c69 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/META-INF/MANIFEST.MF @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: State +Bundle-SymbolicName: org.eclipse.osgi.compatibility.state +Bundle-Version: 1.0.0.qualifier +Fragment-Host: org.eclipse.osgi;bundle-version="3.10.0" +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 diff --git a/bundles/org.eclipse.osgi.compatibility.state/build.properties b/bundles/org.eclipse.osgi.compatibility.state/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/BundleConstraint.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/BundleConstraint.java new file mode 100644 index 000000000..0f933f41a --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/BundleConstraint.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * 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 org.eclipse.osgi.service.resolver.*; + +/* + * A companion to BundleSpecification from the state for use while resolving + */ +public class BundleConstraint extends ResolverConstraint { + BundleConstraint(ResolverBundle bundle, VersionConstraint bundleConstraint) { + super(bundle, bundleConstraint); + } + + boolean isOptional() { + if (constraint instanceof HostSpecification) + return false; + return ((BundleSpecification) constraint).isOptional(); + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/CompositeResolveHelper.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/CompositeResolveHelper.java new file mode 100644 index 000000000..5f7cdba7d --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/CompositeResolveHelper.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2008 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 org.eclipse.osgi.service.resolver.ExportPackageDescription; + +public interface CompositeResolveHelper { + public boolean giveExports(ExportPackageDescription[] matchingExports); +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/CompositeResolveHelperRegistry.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/CompositeResolveHelperRegistry.java new file mode 100644 index 000000000..f4cd6fb80 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/CompositeResolveHelperRegistry.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2008 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 org.eclipse.osgi.service.resolver.BundleDescription; + +public interface CompositeResolveHelperRegistry { + public CompositeResolveHelper getCompositeResolveHelper(BundleDescription bundle); +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/GenericCapability.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/GenericCapability.java new file mode 100644 index 000000000..3156e00fa --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/GenericCapability.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2006, 2011 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 org.eclipse.osgi.service.resolver.BundleDescription; +import org.eclipse.osgi.service.resolver.GenericDescription; +import org.eclipse.osgi.util.ManifestElement; +import org.osgi.framework.Constants; + +public class GenericCapability extends VersionSupplier { + final ResolverBundle resolverBundle; + final String[] uses; + final boolean effective; + + GenericCapability(ResolverBundle resolverBundle, GenericDescription base) { + super(base); + this.resolverBundle = resolverBundle; + String usesDirective = base.getDeclaredDirectives().get(Constants.USES_DIRECTIVE); + uses = ManifestElement.getArrayFromList(usesDirective); + String effectiveDirective = base.getDeclaredDirectives().get(Constants.EFFECTIVE_DIRECTIVE); + effective = effectiveDirective == null || Constants.EFFECTIVE_RESOLVE.equals(effectiveDirective); + } + + public BundleDescription getBundleDescription() { + return getBaseDescription().getSupplier(); + } + + GenericDescription getGenericDescription() { + return (GenericDescription) getBaseDescription(); + } + + public ResolverBundle getResolverBundle() { + return resolverBundle; + } + + String[] getUsesDirective() { + return uses; + } + + boolean isEffective() { + return effective; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/GenericConstraint.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/GenericConstraint.java new file mode 100644 index 000000000..a84a05812 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/GenericConstraint.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2006, 2012 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 org.eclipse.osgi.internal.resolver.GenericSpecificationImpl; +import org.eclipse.osgi.service.resolver.GenericSpecification; +import org.osgi.framework.Constants; + +public class GenericConstraint extends ResolverConstraint { + + private final boolean effective; + private boolean supplierHasUses; + + GenericConstraint(ResolverBundle bundle, GenericSpecification constraint) { + super(bundle, constraint); + String effectiveDirective = constraint.getRequirement().getDirectives().get(Constants.EFFECTIVE_DIRECTIVE); + effective = effectiveDirective == null || Constants.EFFECTIVE_RESOLVE.equals(effectiveDirective); + } + + boolean isOptional() { + return (((GenericSpecification) constraint).getResolution() & GenericSpecification.RESOLUTION_OPTIONAL) != 0; + } + + boolean isFromRequiredEE() { + return (((GenericSpecification) constraint).getResolution() & GenericSpecificationImpl.RESOLUTION_FROM_BREE) != 0; + } + + boolean isMultiple() { + return !supplierHasUses && (((GenericSpecification) constraint).getResolution() & GenericSpecification.RESOLUTION_MULTIPLE) != 0; + } + + boolean isEffective() { + return effective; + } + + public String getNameSpace() { + return ((GenericSpecification) getVersionConstraint()).getType(); + } + + public VersionSupplier[] getMatchingCapabilities() { + if (isMultiple()) + return getPossibleSuppliers(); + VersionSupplier supplier = getSelectedSupplier(); + return supplier == null ? null : new VersionSupplier[] {supplier}; + } + + @Override + void addPossibleSupplier(VersionSupplier supplier) { + // if there is a supplier with uses constraints then we no longer allow multiples + supplierHasUses |= ((GenericCapability) supplier).getUsesDirective() != null; + super.addPossibleSupplier(supplier); + } + + @Override + void clearPossibleSuppliers() { + super.clearPossibleSuppliers(); + supplierHasUses = false; + } + + boolean supplierHasUses() { + return supplierHasUses; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/GroupingChecker.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/GroupingChecker.java new file mode 100644 index 000000000..ea39971bd --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/GroupingChecker.java @@ -0,0 +1,433 @@ +/******************************************************************************* + * Copyright (c) 2004, 2011 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.eclipse.osgi.service.resolver.ExportPackageDescription; + +/* + * The GroupingChecker checks the 'uses' directive on exported packages for consistency + */ +public class GroupingChecker { + final PackageRoots nullPackageRoots = new PackageRoots(null); + // a mapping of bundles to their package roots; keyed by + // ResolverBundle -> HashMap of packages; keyed by + // package name -> PackageRoots + private Map<ResolverBundle, Map<String, PackageRoots>> bundles = new HashMap<ResolverBundle, Map<String, PackageRoots>>(); + + /* + * 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) { + if (bundles.containsKey(bundle)) + // only do the full populate the first time (bug 337272) + return; + // 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<ResolverBundle>(1), true, null); + } + // process all imports + // must check resolved imports to get any dynamically resolved imports + ExportPackageDescription[] imports = bundle.getBundleDescription().getResolvedImports(); + for (ExportPackageDescription importPkg : imports) { + List<ResolverExport> exports = bundle.getResolver().getResolverExports().get(importPkg.getName()); + for (ResolverExport export : exports) { + if (export.getExportPackageDescription() == importPkg) + isConsistentInternal(bundle, export, true, null); + } + } + } + + /* + * Re-populates the package roots or an importing bundle with the given export + * This is done after wiring a package from a dynamic import (bug 337272) + */ + public void populateRoots(ResolverBundle importingBundle, ResolverExport export) { + Map<String, PackageRoots> packageRoots = bundles.get(importingBundle); + if (packageRoots != null) + packageRoots.remove(export.getName()); + PackageRoots roots = getPackageRoots(export.getExporter(), export.getName(), null); + packageRoots.put(export.getName(), roots); + } + + /* + * 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 PackageRoots[][] isConsistent(ResolverBundle requiringBundle, ResolverBundle matchingBundle) { + List<PackageRoots[]> results = isConsistentInternal(requiringBundle, matchingBundle, new ArrayList<ResolverBundle>(1), false, null); + return results == null ? null : results.toArray(new PackageRoots[results.size()][]); + } + + private List<PackageRoots[]> isConsistentInternal(ResolverBundle requiringBundle, ResolverBundle matchingBundle, List<ResolverBundle> visited, boolean dynamicImport, List<PackageRoots[]> results) { + // needed to prevent endless cycles + if (visited.contains(matchingBundle)) + return results; + 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++) { + ResolverExport matchingExport = matchingExports[i]; + if (matchingExports[i].getSubstitute() != null) + matchingExport = (ResolverExport) matchingExports[i].getSubstitute(); + results = isConsistentInternal(requiringBundle, matchingExport, dynamicImport, results); + } + // 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; + results = isConsistentInternal(requiringBundle, reexported, visited, dynamicImport, results); + } + return results; + } + + /* + * 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 PackageRoots[][] isConsistent(ResolverBundle importingBundle, ResolverExport matchingExport) { + List<PackageRoots[]> results = isConsistentInternal(importingBundle, matchingExport, false, null); + return results == null ? null : results.toArray(new PackageRoots[results.size()][]); + } + + public PackageRoots[][] isConsistent(ResolverBundle requiringBundle, GenericCapability matchingCapability) { + String[] uses = matchingCapability.getUsesDirective(); + if (uses == null) + return null; + ArrayList<PackageRoots[]> results = new ArrayList<PackageRoots[]>(0); + for (String usedPackage : uses) { + PackageRoots providingRoots = getPackageRoots(matchingCapability.getResolverBundle(), usedPackage, null); + providingRoots.addConflicts(requiringBundle, usedPackage, null, results); + } + return results.size() == 0 ? null : results.toArray(new PackageRoots[results.size()][]); + } + + /* + * Verifies the uses constraint consistency for the importingBundle with the possible dynamic 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 PackageRoots[][] isDynamicConsistent(ResolverBundle importingBundle, ResolverExport matchingExport) { + List<PackageRoots[]> results = isConsistentInternal(importingBundle, matchingExport, true, null); + return results == null ? null : results.toArray(new PackageRoots[results.size()][]); + } + + private List<PackageRoots[]> isConsistentInternal(ResolverBundle importingBundle, ResolverExport matchingExport, boolean dyanamicImport, List<PackageRoots[]> results) { + PackageRoots exportingRoots = getPackageRoots(matchingExport.getExporter(), matchingExport.getName(), null); + // check that the exports uses packages are consistent with existing package roots + results = exportingRoots.isConsistentClassSpace(importingBundle, null, results); + if (!dyanamicImport) + return results; + // for dynamic imports we must check that each existing root is consistent with the possible matching export + PackageRoots importingRoots = getPackageRoots(importingBundle, matchingExport.getName(), null); + Map<String, PackageRoots> importingPackages = bundles.get(importingBundle); + if (importingPackages != null) + for (Iterator<PackageRoots> allImportingPackages = importingPackages.values().iterator(); allImportingPackages.hasNext();) { + PackageRoots roots = allImportingPackages.next(); + if (roots != importingRoots) + results = roots.isConsistentClassSpace(exportingRoots, matchingExport.getExporter(), null, results); + } + // We also must check any generic capabilities are consistent + GenericConstraint[] genericRequires = importingBundle.getGenericRequires(); + for (GenericConstraint constraint : genericRequires) { + if (!constraint.supplierHasUses()) + continue; + GenericCapability supplier = (GenericCapability) constraint.getSelectedSupplier(); + String[] uses = supplier.getUsesDirective(); + if (uses != null) + for (String usedPackage : uses) { + if (usedPackage.equals(matchingExport.getName())) { + results = exportingRoots.addConflicts(supplier.getResolverBundle(), usedPackage, null, results); + } + } + } + return results; + } + + /* + * returns package roots for a specific package name for a specific bundle + */ + PackageRoots getPackageRoots(ResolverBundle bundle, String packageName, List<ResolverBundle> visited) { + Map<String, PackageRoots> packages = bundles.get(bundle); + if (packages == null) { + packages = new HashMap<String, PackageRoots>(5); + bundles.put(bundle, packages); + } + PackageRoots packageRoots = packages.get(packageName); + if (packageRoots == null) { + packageRoots = createPackageRoots(bundle, packageName, visited == null ? new ArrayList<ResolverBundle>(1) : visited); + packages.put(packageName, packageRoots); + } + return packageRoots != null ? packageRoots : nullPackageRoots; + } + + private PackageRoots createPackageRoots(ResolverBundle bundle, String packageName, List<ResolverBundle> visited) { + if (visited.contains(bundle)) + return null; + visited.add(bundle); // prevent endless cycles + // check imports + if (bundle.getBundleDescription().isResolved()) { + // must check resolved imports to get any dynamically resolved imports + ExportPackageDescription[] imports = bundle.getBundleDescription().getResolvedImports(); + for (ExportPackageDescription importPkg : imports) { + if (importPkg.getExporter() == bundle.getBundleDescription() || !importPkg.getName().equals(packageName)) + continue; + List<ResolverExport> exports = bundle.getResolver().getResolverExports().get(packageName); + for (ResolverExport export : exports) { + if (export.getExportPackageDescription() == importPkg) + return getPackageRoots(export.getExporter(), packageName, visited); + } + } + } else { + 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 if the bundle exports the package + ResolverExport[] exports = bundle.getExports(packageName); + List<PackageRoots> roots = new ArrayList<PackageRoots>(0); + // check roots from required bundles + BundleConstraint[] requires = bundle.getRequires(); + 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 != nullPackageRoots) + 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 (reExportedRoots != nullPackageRoots) + roots.add(reExportedRoots); + } + } + } + } + if (exports.length > 0 || roots.size() > 1) { + PackageRoots[] requiredRoots = roots.toArray(new PackageRoots[roots.size()]); + if (exports.length == 0) { + PackageRoots superSet = requiredRoots[0]; + for (int i = 1; i < requiredRoots.length; i++) { + if (requiredRoots[i].superSet(superSet)) { + superSet = requiredRoots[i]; + } else if (!superSet.superSet(requiredRoots[i])) { + superSet = null; + break; + } + } + if (superSet != null) + return superSet; + } + // in this case we cannot share the package roots object; must create one specific for this bundle + PackageRoots result = new PackageRoots(packageName); + // first merge all the roots from required bundles + for (int i = 0; i < requiredRoots.length; i++) + result.merge(requiredRoots[i]); + // always add this bundles exports to the end if it exports the package + for (int i = 0; i < exports.length; i++) + result.addRoot(exports[i]); + return result; + } + return roots.size() == 0 ? nullPackageRoots : roots.get(0); + } + + public void clear() { + bundles.clear(); + } + + public void clear(ResolverBundle rb) { + bundles.remove(rb); + } + + class PackageRoots { + private String name; + private ResolverExport[] roots; + + PackageRoots(String name) { + this.name = name; + } + + public boolean hasRoots() { + return roots != null && roots.length > 0; + } + + 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 (export.getExporter() != roots[i].getExporter() && 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; + } + } + + private boolean contains(ResolverExport export, ResolverExport[] exports) { + for (int i = 0; i < exports.length; i++) + if (exports[i] == export) + return true; + return false; + } + + 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 List<PackageRoots[]> isConsistentClassSpace(ResolverBundle importingBundle, List<PackageRoots> visited, List<PackageRoots[]> results) { + if (roots == null) + return results; + if (visited == null) + visited = new ArrayList<PackageRoots>(1); + if (visited.contains(this)) + return results; + visited.add(this); + int size = roots.length; + for (int i = 0; i < size; i++) { + ResolverExport root = roots[i]; + String[] uses = root.getUsesDirective(); + if (uses == null) + continue; + 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) + continue; + if (thisUsedRoots != nullPackageRoots && importingUsedRoots != nullPackageRoots) + if (!(subSet(thisUsedRoots.roots, importingUsedRoots.roots) || subSet(importingUsedRoots.roots, thisUsedRoots.roots))) { + if (results == null) + results = new ArrayList<PackageRoots[]>(1); + results.add(new PackageRoots[] {this, importingUsedRoots}); + } + // need to check the usedRoots consistency for transitive closure + results = thisUsedRoots.isConsistentClassSpace(importingBundle, visited, results); + } + } + return results; + } + + public List<PackageRoots[]> isConsistentClassSpace(PackageRoots exportingRoots, ResolverBundle exporter, List<PackageRoots> visited, List<PackageRoots[]> results) { + if (roots == null) + return results; + 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<PackageRoots>(1); + if (visited.contains(this)) + return results; + 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(exporter, uses[j], null); + if (thisUsedRoots == exportingRoots) + return results; + if (thisUsedRoots != nullPackageRoots && exportingUsedRoots != nullPackageRoots) + if (!(subSet(thisUsedRoots.roots, exportingUsedRoots.roots) || subSet(exportingUsedRoots.roots, thisUsedRoots.roots))) { + if (results == null) + results = new ArrayList<PackageRoots[]>(1); + results.add(new PackageRoots[] {this, exportingUsedRoots}); + } + // need to check the usedRoots consistency for transitive closure + results = thisUsedRoots.isConsistentClassSpace(exportingRoots, exporter, visited, results); + } + } + return results; + } + + List<PackageRoots[]> addConflicts(ResolverBundle bundle, String usedPackage, List<PackageRoots> visited, List<PackageRoots[]> results) { + PackageRoots bundleUsedRoots = getPackageRoots(bundle, usedPackage, null); + if (this == bundleUsedRoots) + return results; + if (this != nullPackageRoots && bundleUsedRoots != nullPackageRoots) + if (!(subSet(this.roots, bundleUsedRoots.roots) || subSet(bundleUsedRoots.roots, this.roots))) { + if (results == null) + results = new ArrayList<PackageRoots[]>(1); + results.add(new PackageRoots[] {this, bundleUsedRoots}); + } + // need to check the usedRoots consistency for transitive closure + return this.isConsistentClassSpace(bundleUsedRoots, bundle, visited, results); + } + + // 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++) + // compare by exporter in case the bundle exports the package multiple times + if (subSet[i].getExporter() == superSet[j].getExporter()) { + found = true; + break; + } + if (!found) + return false; + } + return true; + } + + public boolean superSet(PackageRoots subSet) { + return subSet(roots, subSet.roots); + } + + public String getName() { + return name; + } + + public ResolverExport[] getRoots() { + return roots; + } + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/MappedList.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/MappedList.java new file mode 100644 index 000000000..a104a2434 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/MappedList.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 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.*; + +/* + * A MappedList maps values into keyed list arrays. All values with the same key are stored + * into the same array. Extending classes may override the sort method to sort the individual + * arrays in the MappedList. By default the MappedList appends new values to the end of the array. + */ +public class MappedList<K, V> { + // the mapping with key -> Object[] mapping + protected final HashMap<K, List<V>> internal = new HashMap<K, List<V>>(); + @SuppressWarnings("unchecked") + protected final List<V> empty = Collections.EMPTY_LIST; + + public void put(K key, V value) { + List<V> existing = internal.get(key); + if (existing == null) { + existing = new ArrayList<V>(1); + existing.add(value); + internal.put(key, existing); + return; + } + // insert the new value + int index = insertionIndex(existing, value); + existing.add(index, value); + } + + protected int insertionIndex(List<V> existing, V value) { + // a MappedList is by default not sorted so just insert at the end + // extending classes may override this method to provide an index that retains sorted order + return existing.size(); + } + + // removes all values with the specified key + public List<V> remove(K key) { + return get(key, true); + } + + // gets all values with the specified key + public List<V> get(K key) { + return get(key, false); + } + + // gets all values with the specified and optionally removes them + private List<V> get(K key, boolean remove) { + List<V> result = remove ? internal.remove(key) : internal.get(key); + return result == null ? empty : result; + } + + // returns the number of keyed lists + public int getSize() { + return internal.size(); + } + + // returns all values of all keys + public List<V> getAllValues() { + if (getSize() == 0) + return empty; + ArrayList<V> results = new ArrayList<V>(getSize()); + Iterator<List<V>> iter = internal.values().iterator(); + while (iter.hasNext()) + results.addAll(iter.next()); + return results; + } + + // removes all keys from the map + public void clear() { + internal.clear(); + } + +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/PermissionChecker.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/PermissionChecker.java new file mode 100644 index 000000000..824de81f9 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/PermissionChecker.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2004, 2010 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.security.Permission; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.*; + +public class PermissionChecker { + private boolean checkPermissions = false; + private ResolverImpl resolver; + + public PermissionChecker(boolean checkPermissions, ResolverImpl resolver) { + this.checkPermissions = checkPermissions; + this.resolver = resolver; + } + + /* + * checks the permission for a bundle to import/reqiure a constraint + * and for a bundle to export/provide a package/BSN + */ + public boolean checkPermission(VersionConstraint vc, BaseDescription bd) { + if (!checkPermissions) + return true; + // TODO could optimize out the producer permission check on export package + boolean success = false; + Permission producerPermission = null, consumerPermission = null; + Bundle consumer = null; + Bundle producer = bd.getSupplier().getBundle(); + int errorType = 0; + if (vc instanceof ImportPackageSpecification) { + errorType = ResolverError.IMPORT_PACKAGE_PERMISSION; + producerPermission = new PackagePermission(bd.getName(), PackagePermission.EXPORTONLY); + consumerPermission = producer != null ? new PackagePermission(vc.getName(), producer, PackagePermission.IMPORT) : new PackagePermission(vc.getName(), PackagePermission.IMPORT); + } else if (vc instanceof BundleSpecification || vc instanceof HostSpecification) { + boolean requireBundle = vc instanceof BundleSpecification; + errorType = requireBundle ? ResolverError.REQUIRE_BUNDLE_PERMISSION : ResolverError.FRAGMENT_BUNDLE_PERMISSION; + producerPermission = new BundlePermission(bd.getName(), requireBundle ? BundlePermission.PROVIDE : BundlePermission.HOST); + consumerPermission = new BundlePermission(vc.getName(), requireBundle ? BundlePermission.REQUIRE : BundlePermission.FRAGMENT); + } else if (vc instanceof GenericSpecification) { + errorType = ResolverError.REQUIRE_CAPABILITY_PERMISSION; + GenericDescription gd = (GenericDescription) bd; + producerPermission = new CapabilityPermission(gd.getType(), CapabilityPermission.PROVIDE); + consumerPermission = new CapabilityPermission(gd.getType(), gd.getDeclaredAttributes(), producer, CapabilityPermission.REQUIRE); + } + consumer = vc.getBundle().getBundle(); + if (producer != null && (producer.getState() & Bundle.UNINSTALLED) == 0) { + success = producer.hasPermission(producerPermission); + if (!success) { + switch (errorType) { + case ResolverError.IMPORT_PACKAGE_PERMISSION : + errorType = ResolverError.EXPORT_PACKAGE_PERMISSION; + break; + case ResolverError.REQUIRE_BUNDLE_PERMISSION : + case ResolverError.FRAGMENT_BUNDLE_PERMISSION : + errorType = errorType == ResolverError.REQUIRE_BUNDLE_PERMISSION ? ResolverError.PROVIDE_BUNDLE_PERMISSION : ResolverError.HOST_BUNDLE_PERMISSION; + break; + case ResolverError.REQUIRE_CAPABILITY_PERMISSION : + errorType = ResolverError.PROVIDE_BUNDLE_PERMISSION; + break; + } + resolver.getState().addResolverError(vc.getBundle(), errorType, producerPermission.toString(), vc); + } + } + if (success && consumer != null && (consumer.getState() & Bundle.UNINSTALLED) == 0) { + success = consumer.hasPermission(consumerPermission); + if (!success) + resolver.getState().addResolverError(vc.getBundle(), errorType, consumerPermission.toString(), vc); + } + + return success; + } + + boolean checkPackagePermission(ExportPackageDescription export) { + if (!checkPermissions) + return true; + export.getSupplier().getBundle(); + Bundle bundle = export.getSupplier().getBundle(); + return bundle == null ? false : bundle.hasPermission(new PackagePermission(export.getName(), PackagePermission.EXPORTONLY)); + } + + boolean checkCapabilityPermission(GenericDescription capability) { + if (!checkPermissions) + return true; + Bundle bundle = capability.getSupplier().getBundle(); + return bundle == null ? false : bundle.hasPermission(new CapabilityPermission(capability.getType(), CapabilityPermission.PROVIDE)); + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverBundle.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverBundle.java new file mode 100644 index 000000000..8f16f561d --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverBundle.java @@ -0,0 +1,664 @@ +/******************************************************************************* + * Copyright (c) 2004, 2012 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 java.util.Map.Entry; +import org.eclipse.osgi.internal.resolver.*; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.Constants; +import org.osgi.framework.namespace.IdentityNamespace; + +/* + * A companion to BundleDescription from the state used while resolving. + */ +public class ResolverBundle extends VersionSupplier implements Comparable<ResolverBundle> { + public static final int UNRESOLVED = 0; + public static final int RESOLVING = 1; + public static final int RESOLVED = 2; + + private final Long bundleID; + private BundleConstraint host; + private ResolverImport[] imports; + private ResolverExport[] exports; + private BundleConstraint[] requires; + private GenericCapability[] genericCapabilities; + private GenericConstraint[] genericReqiures; + // Fragment support + private ArrayList<ResolverBundle> fragments; + private HashMap<Long, List<ResolverExport>> fragmentExports; + private HashMap<Long, List<ResolverImport>> fragmentImports; + private HashMap<Long, List<BundleConstraint>> fragmentRequires; + private HashMap<Long, List<GenericCapability>> fragmentGenericCapabilities; + private HashMap<Long, List<GenericConstraint>> fragmentGenericRequires; + // Flag specifying whether this bundle is resolvable + private boolean resolvable = true; + // Internal resolver state for this bundle + private int state = UNRESOLVED; + private boolean uninstalled = false; + private final ResolverImpl resolver; + private boolean newFragmentExports; + private boolean newFragmentCapabilities; + + ResolverBundle(BundleDescription bundle, ResolverImpl resolver) { + super(bundle); + this.bundleID = new Long(bundle.getBundleId()); + this.resolver = resolver; + initialize(bundle.isResolved()); + } + + void initialize(boolean useSelectedExports) { + if (getBundleDescription().getHost() != null) { + host = new BundleConstraint(this, getBundleDescription().getHost()); + exports = new ResolverExport[0]; + imports = new ResolverImport[0]; + requires = new BundleConstraint[0]; + GenericSpecification[] requirements = getBundleDescription().getGenericRequires(); + List<GenericConstraint> constraints = new ArrayList<GenericConstraint>(); + for (GenericSpecification requirement : requirements) { + if (StateImpl.OSGI_EE_NAMESPACE.equals(requirement.getType())) + constraints.add(new GenericConstraint(this, requirement)); + } + genericReqiures = constraints.toArray(new GenericConstraint[constraints.size()]); + GenericDescription[] capabilities = getBundleDescription().getGenericCapabilities(); + GenericCapability identity = null; + for (GenericDescription capability : capabilities) { + if (IdentityNamespace.IDENTITY_NAMESPACE.equals(capability.getType())) { + identity = new GenericCapability(this, capability); + break; + } + } + + genericCapabilities = identity == null ? new GenericCapability[0] : new GenericCapability[] {identity}; + return; + } + + ImportPackageSpecification[] actualImports = getBundleDescription().getImportPackages(); + // Reorder imports so that optionals are at the end so that we wire statics before optionals + List<ResolverImport> importList = new ArrayList<ResolverImport>(actualImports.length); + for (int i = actualImports.length - 1; i >= 0; i--) + if (ImportPackageSpecification.RESOLUTION_OPTIONAL.equals(actualImports[i].getDirective(Constants.RESOLUTION_DIRECTIVE))) + importList.add(new ResolverImport(this, actualImports[i])); + else + importList.add(0, new ResolverImport(this, actualImports[i])); + imports = importList.toArray(new ResolverImport[importList.size()]); + + ExportPackageDescription[] actualExports = useSelectedExports ? getBundleDescription().getSelectedExports() : getBundleDescription().getExportPackages(); + exports = new ResolverExport[actualExports.length]; + for (int i = 0; i < actualExports.length; i++) + exports[i] = new ResolverExport(this, actualExports[i]); + + BundleSpecification[] actualRequires = getBundleDescription().getRequiredBundles(); + requires = new BundleConstraint[actualRequires.length]; + for (int i = 0; i < requires.length; i++) + requires[i] = new BundleConstraint(this, actualRequires[i]); + + GenericSpecification[] actualGenericRequires = getBundleDescription().getGenericRequires(); + genericReqiures = new GenericConstraint[actualGenericRequires.length]; + for (int i = 0; i < genericReqiures.length; i++) + genericReqiures[i] = new GenericConstraint(this, actualGenericRequires[i]); + + GenericDescription[] actualCapabilities = useSelectedExports ? getBundleDescription().getSelectedGenericCapabilities() : getBundleDescription().getGenericCapabilities(); + genericCapabilities = new GenericCapability[actualCapabilities.length]; + for (int i = 0; i < genericCapabilities.length; i++) + genericCapabilities[i] = new GenericCapability(this, actualCapabilities[i]); + + fragments = null; + fragmentExports = null; + fragmentImports = null; + fragmentRequires = null; + fragmentGenericCapabilities = null; + fragmentGenericRequires = null; + } + + ResolverExport getExport(String name) { + ResolverExport[] allExports = getExports(name); + return allExports.length == 0 ? null : allExports[0]; + } + + ResolverExport[] getExports(String name) { + List<ResolverExport> results = new ArrayList<ResolverExport>(1); // rare to have more than one + // it is faster to ask the VersionHashMap for this package name and then compare the exporter to this + List<ResolverExport> resolverExports = resolver.getResolverExports().get(name); + for (ResolverExport export : resolverExports) + if (export.getExporter() == this) + results.add(export); + return results.toArray(new ResolverExport[results.size()]); + } + + void clearWires() { + ResolverImport[] allImports = getImportPackages(); + for (int i = 0; i < allImports.length; i++) + allImports[i].clearPossibleSuppliers(); + + if (host != null) + host.clearPossibleSuppliers(); + + BundleConstraint[] allRequires = getRequires(); + for (int i = 0; i < allRequires.length; i++) + allRequires[i].clearPossibleSuppliers(); + + GenericConstraint[] allGenericRequires = getGenericRequires(); + for (int i = 0; i < allGenericRequires.length; i++) + allGenericRequires[i].clearPossibleSuppliers(); + + ResolverExport[] allExports = getExportPackages(); + for (int i = 0; i < allExports.length; i++) + allExports[i].setSubstitute(null); + } + + boolean isResolved() { + return getState() == ResolverBundle.RESOLVED; + } + + boolean isFragment() { + return host != null; + } + + int getState() { + return state; + } + + void setState(int state) { + this.state = state; + } + + private <T> List<T> getAll(T[] hostEntries, Map<Long, List<T>> fragmentMap) { + List<T> result = new ArrayList<T>(hostEntries.length); + for (T entry : hostEntries) + result.add(entry); + for (ResolverBundle fragment : fragments) { + List<T> fragEntries = fragmentMap.get(fragment.bundleID); + if (fragEntries != null) + result.addAll(fragEntries); + } + return result; + } + + ResolverImport[] getImportPackages() { + if (isFragment() || fragments == null || fragments.size() == 0) + return imports; + List<ResolverImport> result = getAll(imports, fragmentImports); + return result.toArray(new ResolverImport[result.size()]); + } + + ResolverExport[] getExportPackages() { + if (isFragment() || fragments == null || fragments.size() == 0) + return exports; + List<ResolverExport> result = getAll(exports, fragmentExports); + return result.toArray(new ResolverExport[result.size()]); + } + + ResolverExport[] getSelectedExports() { + return getExports(true); + } + + ResolverExport[] getSubstitutedExports() { + return getExports(false); + } + + private ResolverExport[] getExports(boolean selected) { + ResolverExport[] results = getExportPackages(); + int removedExports = 0; + for (int i = 0; i < results.length; i++) + if (selected ? results[i].getSubstitute() != null : results[i].getSubstitute() == null) + removedExports++; + if (removedExports == 0) + return results; + ResolverExport[] selectedExports = new ResolverExport[results.length - removedExports]; + int index = 0; + for (int i = 0; i < results.length; i++) { + if (selected ? results[i].getSubstitute() != null : results[i].getSubstitute() == null) + continue; + selectedExports[index] = results[i]; + index++; + } + return selectedExports; + } + + BundleConstraint getHost() { + return host; + } + + GenericCapability[] getGenericCapabilities() { + if (isFragment() || fragments == null || fragments.size() == 0) + return genericCapabilities; + List<GenericCapability> result = getAll(genericCapabilities, fragmentGenericCapabilities); + return result.toArray(new GenericCapability[result.size()]); + } + + BundleConstraint[] getRequires() { + if (isFragment() || fragments == null || fragments.size() == 0) + return requires; + List<BundleConstraint> result = getAll(requires, fragmentRequires); + return result.toArray(new BundleConstraint[result.size()]); + } + + GenericConstraint[] getGenericRequires() { + if (isFragment() || fragments == null || fragments.size() == 0) + return genericReqiures; + List<GenericConstraint> result = getAll(genericReqiures, fragmentGenericRequires); + return result.toArray(new GenericConstraint[result.size()]); + } + + BundleConstraint getRequire(String name) { + BundleConstraint[] allRequires = getRequires(); + for (int i = 0; i < allRequires.length; i++) + if (allRequires[i].getVersionConstraint().getName().equals(name)) + return allRequires[i]; + return null; + } + + public BundleDescription getBundleDescription() { + return (BundleDescription) getBaseDescription(); + } + + public ResolverBundle getResolverBundle() { + return this; + } + + ResolverImport getImport(String name) { + ResolverImport[] allImports = getImportPackages(); + for (int i = 0; i < allImports.length; i++) { + if (allImports[i].getName().equals(name)) { + return allImports[i]; + } + } + return null; + } + + public String toString() { + return "[" + getBundleDescription() + "]"; //$NON-NLS-1$ //$NON-NLS-2$ + } + + private void initFragments() { + if (fragments == null) + fragments = new ArrayList<ResolverBundle>(1); + if (fragmentExports == null) + fragmentExports = new HashMap<Long, List<ResolverExport>>(1); + if (fragmentImports == null) + fragmentImports = new HashMap<Long, List<ResolverImport>>(1); + if (fragmentRequires == null) + fragmentRequires = new HashMap<Long, List<BundleConstraint>>(1); + if (fragmentGenericCapabilities == null) + fragmentGenericCapabilities = new HashMap<Long, List<GenericCapability>>(1); + if (fragmentGenericRequires == null) + fragmentGenericRequires = new HashMap<Long, List<GenericConstraint>>(1); + } + + private boolean isImported(String packageName) { + ResolverImport[] allImports = getImportPackages(); + for (int i = 0; i < allImports.length; i++) + if (packageName.equals(allImports[i].getName())) + return true; + + return false; + } + + private boolean isRequired(String bundleName) { + return getRequire(bundleName) != null; + } + + void attachFragment(ResolverBundle fragment, boolean dynamicAttach) { + if (isFragment()) + return; // cannot attach to fragments; + if (!getBundleDescription().attachFragments() || (isResolved() && !getBundleDescription().dynamicFragments())) + return; // host is restricting attachment + if (fragment.getHost().getNumPossibleSuppliers() > 0 && !((HostSpecification) fragment.getHost().getVersionConstraint()).isMultiHost()) + return; // fragment is restricting attachment + + ImportPackageSpecification[] newImports = fragment.getBundleDescription().getImportPackages(); + BundleSpecification[] newRequires = fragment.getBundleDescription().getRequiredBundles(); + ExportPackageDescription[] newExports = fragment.getBundleDescription().getExportPackages(); + GenericDescription[] newGenericCapabilities = fragment.getBundleDescription().getGenericCapabilities(); + GenericSpecification[] newGenericRequires = fragment.getBundleDescription().getGenericRequires(); + + // if this is not during initialization then check if constraints conflict + if (dynamicAttach && constraintsConflict(fragment.getBundleDescription(), newImports, newRequires, newGenericRequires)) + return; // do not allow fragments with conflicting constraints + if (isResolved() && newExports.length > 0) + fragment.setNewFragmentExports(true); + + initFragments(); + // need to make sure there is not already another version of this fragment + // already attached to this host + for (Iterator<ResolverBundle> iFragments = fragments.iterator(); iFragments.hasNext();) { + ResolverBundle existingFragment = iFragments.next(); + String bsn = existingFragment.getName(); + if (bsn != null && bsn.equals(fragment.getName())) + return; + } + if (fragments.contains(fragment)) + return; + fragments.add(fragment); + fragment.getHost().addPossibleSupplier(this); + + if (newImports.length > 0) { + ArrayList<ResolverImport> hostImports = new ArrayList<ResolverImport>(newImports.length); + for (int i = 0; i < newImports.length; i++) + if (!isImported(newImports[i].getName())) + hostImports.add(new ResolverImport(this, newImports[i])); + fragmentImports.put(fragment.bundleID, hostImports); + } + + if (newRequires.length > 0) { + ArrayList<BundleConstraint> hostRequires = new ArrayList<BundleConstraint>(newRequires.length); + for (int i = 0; i < newRequires.length; i++) + if (!isRequired(newRequires[i].getName())) + hostRequires.add(new BundleConstraint(this, newRequires[i])); + fragmentRequires.put(fragment.bundleID, hostRequires); + } + + if (newGenericRequires.length > 0) { + ArrayList<GenericConstraint> hostGenericRequires = new ArrayList<GenericConstraint>(newGenericRequires.length); + for (int i = 0; i < newGenericRequires.length; i++) { + // only add namespaces that are not osgi.ee + if (!StateImpl.OSGI_EE_NAMESPACE.equals(newGenericRequires[i].getType())) + hostGenericRequires.add(new GenericConstraint(this, newGenericRequires[i])); + } + if (!hostGenericRequires.isEmpty()) + fragmentGenericRequires.put(fragment.bundleID, hostGenericRequires); + } + + ArrayList<ResolverExport> hostExports = new ArrayList<ResolverExport>(newExports.length); + if (newExports.length > 0 && dynamicAttach) { + for (int i = 0; i < newExports.length; i++) { + ResolverExport currentExports[] = getExports(newExports[i].getName()); + boolean foundEquivalent = false; + for (int j = 0; j < currentExports.length && !foundEquivalent; j++) { + if (equivalentExports(currentExports[j], newExports[i])) + foundEquivalent = true; + } + if (!foundEquivalent) { + ExportPackageDescription hostExport = new ExportPackageDescriptionImpl(getBundleDescription(), newExports[i]); + hostExports.add(new ResolverExport(this, hostExport)); + } + } + fragmentExports.put(fragment.bundleID, hostExports); + } + + List<GenericCapability> hostCapabilities = new ArrayList<GenericCapability>(newGenericCapabilities.length); + if (newGenericCapabilities.length > 0 && dynamicAttach) { + for (GenericDescription capability : newGenericCapabilities) { + if (!IdentityNamespace.IDENTITY_NAMESPACE.equals(capability.getType())) { + GenericDescription hostCapabililty = new GenericDescriptionImpl(getBundleDescription(), capability); + hostCapabilities.add(new GenericCapability(this, hostCapabililty)); + } + } + if (hostCapabilities.size() > 0) { + fragmentGenericCapabilities.put(fragment.bundleID, hostCapabilities); + if (isResolved()) + fragment.setNewFragmentCapabilities(true); + } + } + if (dynamicAttach) { + resolver.getResolverExports().put(hostExports.toArray(new ResolverExport[hostExports.size()])); + resolver.addGenerics(hostCapabilities.toArray(new GenericCapability[hostCapabilities.size()])); + } + } + + private boolean equivalentExports(ResolverExport existingExport, ExportPackageDescription newDescription) { + ExportPackageDescription existingDescription = existingExport.getExportPackageDescription(); + if (!existingDescription.getName().equals(newDescription.getName())) + return false; + if (!existingDescription.getVersion().equals(newDescription.getVersion())) + return false; + if (!equivalentMaps(existingDescription.getAttributes(), newDescription.getAttributes(), true)) + return false; + if (!equivalentMaps(existingDescription.getDirectives(), newDescription.getDirectives(), true)) + return false; + return true; + } + + public static boolean equivalentMaps(Map<String, Object> existingDirectives, Map<String, Object> newDirectives, boolean exactMatch) { + if (existingDirectives == null && newDirectives == null) + return true; + if (existingDirectives == null ? newDirectives != null : newDirectives == null) + return false; + if (exactMatch && existingDirectives.size() != newDirectives.size()) + return false; + for (Iterator<Entry<String, Object>> entries = existingDirectives.entrySet().iterator(); entries.hasNext();) { + Entry<String, Object> entry = entries.next(); + Object newValue = newDirectives.get(entry.getKey()); + if (newValue == null || entry.getValue().getClass() != newValue.getClass()) + return false; + if (newValue instanceof String[]) { + if (!Arrays.equals((Object[]) entry.getValue(), (Object[]) newValue)) + return false; + } else if (!entry.getValue().equals(newValue)) { + return false; + } + } + return true; + } + + boolean constraintsConflict(BundleDescription fragment, ImportPackageSpecification[] newImports, BundleSpecification[] newRequires, GenericSpecification[] newGenericRequires) { + // this method iterates over all additional constraints from a fragment + // if the host is resolved then the fragment is not allowed to add new constraints; + // if the host is resolved and it already has a constraint of the same name then ensure the supplier satisfies the fragment's constraint + boolean result = false; + for (int i = 0; i < newImports.length; i++) { + ResolverImport hostImport = getImport(newImports[i].getName()); + ResolverExport resolvedExport = (ResolverExport) (hostImport == null ? null : hostImport.getSelectedSupplier()); + if (importPackageConflict(resolvedExport, newImports[i])) { + result = true; + resolver.getState().addResolverError(fragment, ResolverError.FRAGMENT_CONFLICT, newImports[i].toString(), newImports[i]); + } + } + for (int i = 0; i < newRequires.length; i++) { + BundleConstraint hostRequire = getRequire(newRequires[i].getName()); + ResolverBundle resolvedRequire = (ResolverBundle) (hostRequire == null ? null : hostRequire.getSelectedSupplier()); + if ((resolvedRequire == null && isResolved()) || (resolvedRequire != null && !newRequires[i].isSatisfiedBy(resolvedRequire.getBundleDescription()))) { + result = true; + resolver.getState().addResolverError(fragment, ResolverError.FRAGMENT_CONFLICT, newRequires[i].toString(), newRequires[i]); + } + } + // generic constraints cannot conflict; + // only check that a fragment does not add generic constraints to an already resolved host + if (isResolved() && newGenericRequires != null) { + for (GenericSpecification genericSpecification : newGenericRequires) { + if (!StateImpl.OSGI_EE_NAMESPACE.equals(genericSpecification.getType())) { + result = true; + resolver.getState().addResolverError(fragment, ResolverError.FRAGMENT_CONFLICT, genericSpecification.toString(), genericSpecification); + } + } + } + return result; + } + + private boolean importPackageConflict(ResolverExport resolvedExport, ImportPackageSpecification newImport) { + if (resolvedExport == null) + return isResolved(); + return !((ImportPackageSpecificationImpl) newImport).isSatisfiedBy(resolvedExport.getExportPackageDescription(), false); + } + + private void setNewFragmentExports(boolean newFragmentExports) { + this.newFragmentExports = newFragmentExports; + } + + boolean isNewFragmentExports() { + return newFragmentExports; + } + + private void setNewFragmentCapabilities(boolean newFragmentCapabilities) { + this.newFragmentCapabilities = newFragmentCapabilities; + } + + boolean isNewFragmentCapabilities() { + return newFragmentCapabilities; + } + + public void detachFromHosts() { + if (!isFragment()) { + return; + } + VersionSupplier[] hosts = getHost().getPossibleSuppliers(); + if (hosts == null) { + return; + } + for (VersionSupplier possibleHost : hosts) { + ((ResolverBundle) possibleHost).detachFragment(this, null); + } + } + + void detachFragment(ResolverBundle fragment, ResolverConstraint reason) { + if (isFragment()) + return; + initFragments(); + + // must save off old imports and requires before we remove the fragment; + // this will be used to merge the constraints of the same name from the remaining fragments + ResolverImport[] oldImports = getImportPackages(); + BundleConstraint[] oldRequires = getRequires(); + if (!fragments.remove(fragment)) + return; + + fragment.setNewFragmentExports(false); + fragment.setNewFragmentCapabilities(false); + fragment.getHost().removePossibleSupplier(this); + fragmentImports.remove(fragment.bundleID); + fragmentRequires.remove(fragment.bundleID); + List<ResolverExport> removedExports = fragmentExports.remove(fragment.bundleID); + fragmentGenericRequires.remove(fragment.bundleID); + List<GenericCapability> removedCapabilities = fragmentGenericCapabilities.remove(fragment.bundleID); + if (reason != null) { + // the fragment is being detached because one of its imports or requires cannot be resolved; + // we need to check the remaining fragment constraints to make sure they do not have + // the same unresolved constraint. + // bug 353103: must make a snapshot to avoid ConcurrentModificationException + ResolverBundle[] remainingFrags = fragments.toArray(new ResolverBundle[fragments.size()]); + for (ResolverBundle remainingFrag : remainingFrags) { + List<ResolverImport> additionalImports = new ArrayList<ResolverImport>(0); + List<BundleConstraint> additionalRequires = new ArrayList<BundleConstraint>(0); + if (hasUnresolvedConstraint(reason, fragment, remainingFrag, oldImports, oldRequires, additionalImports, additionalRequires)) + continue; + // merge back the additional imports or requires which the detached fragment has in common with the remaining fragment + if (additionalImports.size() > 0) { + List<ResolverImport> remainingImports = fragmentImports.get(remainingFrag.bundleID); + if (remainingImports == null) + fragmentImports.put(remainingFrag.bundleID, additionalImports); + else + remainingImports.addAll(additionalImports); + } + if (additionalRequires.size() > 0) { + List<BundleConstraint> remainingRequires = fragmentRequires.get(remainingFrag.bundleID); + if (remainingRequires == null) + fragmentRequires.put(remainingFrag.bundleID, additionalRequires); + else + remainingRequires.addAll(additionalRequires); + } + } + } + ResolverExport[] results = removedExports == null ? new ResolverExport[0] : removedExports.toArray(new ResolverExport[removedExports.size()]); + for (int i = 0; i < results.length; i++) + // TODO this is a hack; need to figure out how to indicate that a fragment export is no longer attached + results[i].setSubstitute(results[i]); + resolver.getResolverExports().remove(results); + if (removedCapabilities != null) + resolver.removeGenerics(removedCapabilities.toArray(new GenericCapability[removedCapabilities.size()])); + } + + private boolean hasUnresolvedConstraint(ResolverConstraint reason, ResolverBundle detachedFragment, ResolverBundle remainingFragment, ResolverImport[] oldImports, BundleConstraint[] oldRequires, List<ResolverImport> additionalImports, List<BundleConstraint> additionalRequires) { + ImportPackageSpecification[] remainingFragImports = remainingFragment.getBundleDescription().getImportPackages(); + BundleSpecification[] remainingFragRequires = remainingFragment.getBundleDescription().getRequiredBundles(); + VersionConstraint[] constraints; + if (reason instanceof ResolverImport) + constraints = remainingFragImports; + else + constraints = remainingFragRequires; + for (int i = 0; i < constraints.length; i++) + if (reason.getName().equals(constraints[i].getName())) { + detachFragment(remainingFragment, reason); + return true; + } + for (int i = 0; i < oldImports.length; i++) { + if (oldImports[i].getVersionConstraint().getBundle() != detachedFragment.getBundleDescription()) + continue; // the constraint is not from the detached fragment + for (int j = 0; j < remainingFragImports.length; j++) { + if (oldImports[i].getName().equals(remainingFragImports[j].getName())) { + // same constraint, must reuse the constraint object but swap out the fragment info + additionalImports.add(oldImports[i]); + oldImports[i].setVersionConstraint(remainingFragImports[j]); + break; + } + } + } + for (int i = 0; i < oldRequires.length; i++) { + if (oldRequires[i].getVersionConstraint().getBundle() != detachedFragment.getBundleDescription()) + continue; // the constraint is not from the detached fragment + for (int j = 0; j < remainingFragRequires.length; j++) { + if (oldRequires[i].getName().equals(remainingFragRequires[j].getName())) { + // same constraint, must reuse the constraint object but swap out the fragment info + additionalRequires.add(oldRequires[i]); + oldRequires[i].setVersionConstraint(remainingFragRequires[j]); + break; + } + } + } + return false; + } + + void detachAllFragments() { + if (fragments == null) + return; + ResolverBundle[] allFragments = fragments.toArray(new ResolverBundle[fragments.size()]); + for (int i = 0; i < allFragments.length; i++) + detachFragment(allFragments[i], null); + fragments = null; + } + + boolean isResolvable() { + return resolvable; + } + + void setResolvable(boolean resolvable) { + this.resolvable = resolvable; + } + + void addExport(ResolverExport re) { + ResolverExport[] newExports = new ResolverExport[exports.length + 1]; + for (int i = 0; i < exports.length; i++) + newExports[i] = exports[i]; + newExports[exports.length] = re; + exports = newExports; + } + + ResolverImpl getResolver() { + return resolver; + } + + ResolverBundle[] getFragments() { + return fragments == null ? new ResolverBundle[0] : (ResolverBundle[]) fragments.toArray(new ResolverBundle[fragments.size()]); + } + + /* + * This is used to sort bundles by BSN. This is needed to fix bug 174930 + * If both BSNs are null then 0 is returned + * If this BSN is null the 1 is returned + * If the other BSN is null then -1 is returned + * otherwise String.compareTo is used + */ + public int compareTo(ResolverBundle o) { + String bsn = getName(); + String otherBsn = o.getName(); + if (bsn == null) + return otherBsn == null ? 0 : 1; + return otherBsn == null ? -1 : bsn.compareTo(otherBsn); + } + + void setUninstalled() { + uninstalled = true; + } + + boolean isUninstalled() { + return uninstalled; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverConstraint.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverConstraint.java new file mode 100644 index 000000000..759898a70 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverConstraint.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2005, 2011 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 org.eclipse.osgi.service.resolver.BundleDescription; +import org.eclipse.osgi.service.resolver.VersionConstraint; +import org.osgi.framework.wiring.BundleRequirement; + +/* + * A companion to VersionConstraint from the state used while resolving + */ +public abstract class ResolverConstraint { + final protected ResolverBundle bundle; + protected VersionConstraint constraint; + private BundleRequirement requrement; + private VersionSupplier[] possibleSuppliers; + private int selectedSupplierIndex = 0; + + ResolverConstraint(ResolverBundle bundle, VersionConstraint constraint) { + this.bundle = bundle; + this.constraint = constraint; + this.requrement = constraint.getRequirement(); + } + + // returns the Resolver bundle requiring the ResolverConstraint + ResolverBundle getBundle() { + return bundle; + } + + // returns the BundleDescription requiring the ResolverConstraint + BundleDescription getBundleDescription() { + return bundle.getBundleDescription(); + } + + // returns whether this constraint is from an attached fragment + boolean isFromFragment() { + return constraint.getBundle().getHost() != null; + } + + // Same as VersionConstraint but does additinal permission checks + boolean isSatisfiedBy(VersionSupplier vs) { + if (vs.getResolverBundle().isUninstalled() || !bundle.getResolver().getPermissionChecker().checkPermission(constraint, vs.getBaseDescription())) + return false; + return vs.getSubstitute() == null && constraint.isSatisfiedBy(vs.getBaseDescription()); + } + + // returns the companion VersionConstraint object from the State + VersionConstraint getVersionConstraint() { + return constraint; + } + + // returns the name of this constraint + public String getName() { + return constraint.getName(); + } + + public String toString() { + return constraint.toString(); + } + + // returns whether this constraint is optional + abstract boolean isOptional(); + + 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); + possibleSuppliers = newSuppliers; + } + } + + 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; + } + + void setVersionConstraint(VersionConstraint constraint) { + this.constraint = constraint; + this.requrement = constraint.getRequirement(); + } + + BundleRequirement getRequirement() { + return requrement; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverExport.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverExport.java new file mode 100644 index 000000000..7aab84b87 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverExport.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2004, 2010 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 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. + */ +public class ResolverExport extends VersionSupplier { + private ResolverBundle resolverBundle; + + ResolverExport(ResolverBundle resolverBundle, ExportPackageDescription epd) { + super(epd); + this.resolverBundle = resolverBundle; + } + + public ExportPackageDescription getExportPackageDescription() { + return (ExportPackageDescription) base; + } + + public BundleDescription getBundleDescription() { + return getExportPackageDescription().getExporter(); + } + + ResolverBundle getExporter() { + return resolverBundle; + } + + ResolverBundle getResolverBundle() { + return getExporter(); + } + + String[] getUsesDirective() { + return (String[]) getExportPackageDescription().getDirective(Constants.USES_DIRECTIVE); + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverImpl.java new file mode 100644 index 000000000..6a97c2159 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverImpl.java @@ -0,0 +1,2243 @@ +/******************************************************************************* + * Copyright (c) 2004, 2012 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 + * Danail Nachev - ProSyst - bug 218625 + * Rob Harrop - SpringSource Inc. (bug 247522) + ******************************************************************************/ +package org.eclipse.osgi.internal.module; + +import org.eclipse.osgi.internal.framework.EquinoxContainer; +import org.eclipse.osgi.internal.framework.FilterImpl; + +import org.eclipse.osgi.internal.debug.Debug; +import org.eclipse.osgi.internal.debug.FrameworkDebugOptions; + +import java.security.AccessController; +import java.util.*; +import org.eclipse.osgi.framework.util.SecureAction; +import org.eclipse.osgi.internal.baseadaptor.ArrayMap; +import org.eclipse.osgi.internal.module.GroupingChecker.PackageRoots; +import org.eclipse.osgi.internal.resolver.*; +import org.eclipse.osgi.service.resolver.*; +import org.eclipse.osgi.util.ManifestElement; +import org.osgi.framework.Constants; +import org.osgi.framework.Filter; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.hooks.resolver.ResolverHook; +import org.osgi.framework.namespace.IdentityNamespace; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleRevision; + +public class ResolverImpl implements Resolver { + // Debug fields + private static final String RESOLVER = EquinoxContainer.NAME + "/resolver"; //$NON-NLS-1$ + private static final String OPTION_DEBUG = RESOLVER + "/debug";//$NON-NLS-1$ + private static final String OPTION_WIRING = RESOLVER + "/wiring"; //$NON-NLS-1$ + private static final String OPTION_IMPORTS = RESOLVER + "/imports"; //$NON-NLS-1$ + private static final String OPTION_REQUIRES = RESOLVER + "/requires"; //$NON-NLS-1$ + private static final String OPTION_GENERICS = RESOLVER + "/generics"; //$NON-NLS-1$ + private static final String OPTION_USES = RESOLVER + "/uses"; //$NON-NLS-1$ + private static final String OPTION_CYCLES = RESOLVER + "/cycles"; //$NON-NLS-1$ + public static boolean DEBUG = false; + public static boolean DEBUG_WIRING = false; + public static boolean DEBUG_IMPORTS = false; + public static boolean DEBUG_REQUIRES = false; + public static boolean DEBUG_GENERICS = false; + public static boolean DEBUG_USES = false; + public static boolean DEBUG_CYCLES = false; + private static int MAX_MULTIPLE_SUPPLIERS_MERGE = 10; + private static int MAX_USES_TIME_BASE = 30000; // 30 seconds + private static int MAX_USES_TIME_LIMIT = 90000; // 90 seconds + private static final String USES_TIMEOUT_PROP = "osgi.usesTimeout"; //$NON-NLS-1$ + private static final String MULTIPLE_SUPPLIERS_LIMIT_PROP = "osgi.usesLimit"; //$NON-NLS-1$ + static final SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction()); + + private String[][] CURRENT_EES; + private ResolverHook hook; + + // The State associated with this resolver + private State state; + // Used to check permissions for import/export, provide/require, host/fragment + private final PermissionChecker permissionChecker; + // Set of bundles that are pending removal + private MappedList<Long, BundleDescription> removalPending = new MappedList<Long, BundleDescription>(); + // Indicates whether this resolver has been initialized + private boolean initialized = false; + + // Repository for exports + private VersionHashMap<ResolverExport> resolverExports = null; + // Repository for bundles + private VersionHashMap<ResolverBundle> resolverBundles = null; + // Repository for generics + private Map<String, VersionHashMap<GenericCapability>> resolverGenerics = null; + // List of unresolved bundles + private HashSet<ResolverBundle> unresolvedBundles = null; + // Keys are BundleDescriptions, values are ResolverBundles + private HashMap<BundleDescription, ResolverBundle> bundleMapping = null; + private GroupingChecker groupingChecker; + private Comparator<BaseDescription> selectionPolicy; + private boolean developmentMode = false; + private boolean usesCalculationTimeout = false; + private long usesTimeout = -1; + private int usesMultipleSuppliersLimit; + private volatile CompositeResolveHelperRegistry compositeHelpers; + + public ResolverImpl(boolean checkPermissions) { + this.permissionChecker = new PermissionChecker(checkPermissions, this); + } + + PermissionChecker getPermissionChecker() { + return permissionChecker; + } + + // Initializes the resolver + private void initialize() { + resolverExports = new VersionHashMap<ResolverExport>(this); + resolverBundles = new VersionHashMap<ResolverBundle>(this); + resolverGenerics = new HashMap<String, VersionHashMap<GenericCapability>>(); + unresolvedBundles = new HashSet<ResolverBundle>(); + bundleMapping = new HashMap<BundleDescription, ResolverBundle>(); + BundleDescription[] bundles = state.getBundles(); + groupingChecker = new GroupingChecker(); + + ArrayList<ResolverBundle> fragmentBundles = new ArrayList<ResolverBundle>(); + // Add each bundle to the resolver's internal state + for (int i = 0; i < bundles.length; i++) + initResolverBundle(bundles[i], fragmentBundles, false); + // Add each removal pending bundle to the resolver's internal state + List<BundleDescription> removedBundles = removalPending.getAllValues(); + for (BundleDescription removed : removedBundles) + initResolverBundle(removed, fragmentBundles, true); + // Iterate over the resolved fragments and attach them to their hosts + for (Iterator<ResolverBundle> iter = fragmentBundles.iterator(); iter.hasNext();) { + ResolverBundle fragment = iter.next(); + BundleDescription[] hosts = ((HostSpecification) fragment.getHost().getVersionConstraint()).getHosts(); + for (int i = 0; i < hosts.length; i++) { + ResolverBundle host = bundleMapping.get(hosts[i]); + if (host != null) + // Do not add fragment exports here because they would have been added by the host above. + host.attachFragment(fragment, false); + } + } + rewireBundles(); // Reconstruct wirings + setDebugOptions(); + initialized = true; + } + + private void initResolverBundle(BundleDescription bundleDesc, ArrayList<ResolverBundle> fragmentBundles, boolean pending) { + ResolverBundle bundle = new ResolverBundle(bundleDesc, this); + bundleMapping.put(bundleDesc, bundle); + if (!pending || bundleDesc.isResolved()) { + resolverExports.put(bundle.getExportPackages()); + resolverBundles.put(bundle.getName(), bundle); + addGenerics(bundle.getGenericCapabilities()); + } + if (bundleDesc.isResolved()) { + bundle.setState(ResolverBundle.RESOLVED); + if (bundleDesc.getHost() != null) + fragmentBundles.add(bundle); + } else { + if (!pending) + unresolvedBundles.add(bundle); + } + } + + // Re-wire previously resolved bundles + private void rewireBundles() { + List<ResolverBundle> visited = new ArrayList<ResolverBundle>(bundleMapping.size()); + for (ResolverBundle rb : bundleMapping.values()) { + if (!rb.getBundleDescription().isResolved()) + continue; + rewireBundle(rb, visited); + } + } + + private void rewireBundle(ResolverBundle rb, List<ResolverBundle> visited) { + if (visited.contains(rb)) + return; + visited.add(rb); + // Wire requires to bundles + BundleConstraint[] requires = rb.getRequires(); + for (int i = 0; i < requires.length; i++) { + rewireRequire(requires[i], visited); + } + // Wire imports to exports + ResolverImport[] imports = rb.getImportPackages(); + for (int i = 0; i < imports.length; i++) { + rewireImport(imports[i], visited); + } + // Wire generics + GenericConstraint[] genericRequires = rb.getGenericRequires(); + for (int i = 0; i < genericRequires.length; i++) + rewireGeneric(genericRequires[i], visited); + } + + private void rewireGeneric(GenericConstraint constraint, List<ResolverBundle> visited) { + if (constraint.getSelectedSupplier() != null) + return; + GenericDescription[] suppliers = ((GenericSpecification) constraint.getVersionConstraint()).getSuppliers(); + if (suppliers == null) + return; + VersionHashMap<GenericCapability> namespace = resolverGenerics.get(constraint.getNameSpace()); + if (namespace == null) { + System.err.println("Could not find matching capability for " + constraint.getVersionConstraint()); //$NON-NLS-1$ + // TODO log error!! + return; + } + String constraintName = constraint.getName(); + List<GenericCapability> matches = constraintName == null ? namespace.get(constraintName) : namespace.getAllValues(); + for (GenericCapability match : matches) { + for (GenericDescription supplier : suppliers) + if (match.getBaseDescription() == supplier) + constraint.addPossibleSupplier(match); + } + VersionSupplier[] matchingCapabilities = constraint.getPossibleSuppliers(); + if (matchingCapabilities != null) + for (int i = 0; i < matchingCapabilities.length; i++) + rewireBundle(matchingCapabilities[i].getResolverBundle(), visited); + } + + private void rewireRequire(BundleConstraint req, List<ResolverBundle> visited) { + if (req.getSelectedSupplier() != null) + return; + ResolverBundle matchingBundle = bundleMapping.get(req.getVersionConstraint().getSupplier()); + 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!! + } + if (matchingBundle != null) { + rewireBundle(matchingBundle, visited); + } + } + + private void rewireImport(ResolverImport imp, List<ResolverBundle> visited) { + if (imp.isDynamic() || imp.getSelectedSupplier() != null) + return; + // Re-wire 'imp' + ResolverExport matchingExport = null; + ExportPackageDescription importSupplier = (ExportPackageDescription) imp.getVersionConstraint().getSupplier(); + ResolverBundle exporter = importSupplier == null ? null : (ResolverBundle) bundleMapping.get(importSupplier.getExporter()); + List<ResolverExport> matches = resolverExports.get(imp.getName()); + for (ResolverExport export : matches) { + if (export.getExporter() == exporter && importSupplier == export.getExportPackageDescription()) { + matchingExport = export; + break; + } + } + imp.addPossibleSupplier(matchingExport); + // If we still have a null wire and it's not optional, then we have an error + 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.getSelectedSupplier() != null) { + rewireBundle(((ResolverExport) imp.getSelectedSupplier()).getExporter(), visited); + } + } + + // Checks a bundle to make sure it is valid. If this method returns false for + // a given bundle, then that bundle will not even be considered for resolution + private boolean isResolvable(ResolverBundle bundle, Dictionary<Object, Object>[] platformProperties, Collection<ResolverBundle> hookDisabled) { + BundleDescription bundleDesc = bundle.getBundleDescription(); + + // check if the bundle is a hook disabled bundle + if (hookDisabled.contains(bundle)) { + state.addResolverError(bundleDesc, ResolverError.DISABLED_BUNDLE, "Resolver hook disabled bundle.", null); //$NON-NLS-1$ + return false; + } + // check to see if the bundle is disabled + DisabledInfo[] disabledInfos = state.getDisabledInfos(bundleDesc); + if (disabledInfos.length > 0) { + StringBuffer message = new StringBuffer(); + for (int i = 0; i < disabledInfos.length; i++) { + if (i > 0) + message.append(' '); + message.append('\"').append(disabledInfos[i].getPolicyName()).append(':').append(disabledInfos[i].getMessage()).append('\"'); + } + state.addResolverError(bundleDesc, ResolverError.DISABLED_BUNDLE, message.toString(), null); + return false; // fail because we are disable + } + + // check the required execution environment + String[] ees = bundleDesc.getExecutionEnvironments(); + boolean matchedEE = ees.length == 0; + if (!matchedEE) + for (int i = 0; i < ees.length && !matchedEE; i++) + for (int j = 0; j < CURRENT_EES.length && !matchedEE; j++) + for (int k = 0; k < CURRENT_EES[j].length && !matchedEE; k++) + if (CURRENT_EES[j][k].equals(ees[i])) { + ((BundleDescriptionImpl) bundleDesc).setEquinoxEE(j); + matchedEE = true; + } + if (!matchedEE) { + StringBuffer bundleEE = new StringBuffer(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT.length() + 20); + bundleEE.append(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT).append(": "); //$NON-NLS-1$ + for (int i = 0; i < ees.length; i++) { + if (i > 0) + bundleEE.append(","); //$NON-NLS-1$ + bundleEE.append(ees[i]); + } + state.addResolverError(bundleDesc, ResolverError.MISSING_EXECUTION_ENVIRONMENT, bundleEE.toString(), null); + return false; + } + + // check the native code specification + NativeCodeSpecification nativeCode = bundleDesc.getNativeCodeSpecification(); + if (nativeCode != null) { + NativeCodeDescription[] nativeCodeSuppliers = nativeCode.getPossibleSuppliers(); + NativeCodeDescription highestRanked = null; + for (int i = 0; i < nativeCodeSuppliers.length; i++) + if (nativeCode.isSatisfiedBy(nativeCodeSuppliers[i]) && (highestRanked == null || highestRanked.compareTo(nativeCodeSuppliers[i]) < 0)) + highestRanked = nativeCodeSuppliers[i]; + if (highestRanked == null) { + if (!nativeCode.isOptional()) { + state.addResolverError(bundleDesc, ResolverError.NO_NATIVECODE_MATCH, nativeCode.toString(), nativeCode); + return false; + } + } else { + if (highestRanked.hasInvalidNativePaths()) { + state.addResolverError(bundleDesc, ResolverError.INVALID_NATIVECODE_PATHS, highestRanked.toString(), nativeCode); + return false; + } + } + state.resolveConstraint(nativeCode, highestRanked); + } + + // check the platform filter + String platformFilter = bundleDesc.getPlatformFilter(); + if (platformFilter == null) + return true; + if (platformProperties == null) + return false; + try { + Filter filter = FilterImpl.newInstance(platformFilter); + for (int i = 0; i < platformProperties.length; i++) { + // using matchCase here in case of duplicate case invarient keys (bug 180817) + @SuppressWarnings("rawtypes") + Dictionary props = platformProperties[i]; + if (filter.matchCase(props)) + return true; + } + } catch (InvalidSyntaxException e) { + // return false below + } + state.addResolverError(bundleDesc, ResolverError.PLATFORM_FILTER, platformFilter, null); + return false; + } + + // Attach fragment to its host + private void attachFragment(ResolverBundle bundle, Collection<String> processedFragments) { + if (processedFragments.contains(bundle.getName())) + return; + processedFragments.add(bundle.getName()); + // we want to attach multiple versions of the same fragment + // from highest version to lowest to give the higher versions first pick + // of the available host bundles. + List<ResolverBundle> fragments = resolverBundles.get(bundle.getName()); + for (ResolverBundle fragment : fragments) { + if (!fragment.isResolved()) + attachFragment0(fragment); + } + } + + private void attachFragment0(ResolverBundle bundle) { + if (!bundle.isFragment() || !bundle.isResolvable()) + return; + bundle.clearWires(); + if (!resolveOSGiEE(bundle)) + return; + // no need to select singletons now; it will be done when we select the rest of the singleton bundles (bug 152042) + // find all available hosts to attach to. + boolean foundMatch = false; + BundleConstraint hostConstraint = bundle.getHost(); + long timestamp; + List<ResolverBundle> candidates; + do { + timestamp = state.getTimeStamp(); + List<ResolverBundle> hosts = resolverBundles.get(hostConstraint.getVersionConstraint().getName()); + candidates = new ArrayList<ResolverBundle>(hosts); + List<BundleCapability> hostCapabilities = new ArrayList<BundleCapability>(hosts.size()); + // Must remove candidates that do not match before calling hooks. + for (Iterator<ResolverBundle> iCandidates = candidates.iterator(); iCandidates.hasNext();) { + ResolverBundle host = iCandidates.next(); + if (!host.isResolvable() || !host.getBundleDescription().attachFragments() || !hostConstraint.isSatisfiedBy(host)) { + iCandidates.remove(); + } else { + List<BundleCapability> h = host.getBundleDescription().getDeclaredCapabilities(BundleRevision.HOST_NAMESPACE); + // the bundle must have 1 host capability. + hostCapabilities.add(h.get(0)); + } + } + + if (hook != null) + hook.filterMatches(hostConstraint.getRequirement(), asCapabilities(new ArrayMap<BundleCapability, ResolverBundle>(hostCapabilities, candidates))); + } while (timestamp != state.getTimeStamp()); + // we are left with only candidates that satisfy the host constraint + for (ResolverBundle host : candidates) { + foundMatch = true; + host.attachFragment(bundle, true); + } + if (!foundMatch) + state.addResolverError(bundle.getBundleDescription(), ResolverError.MISSING_FRAGMENT_HOST, bundle.getHost().getVersionConstraint().toString(), bundle.getHost().getVersionConstraint()); + + } + + private boolean resolveOSGiEE(ResolverBundle bundle) { + GenericConstraint[] requirements = bundle.getGenericRequires(); + for (GenericConstraint requirement : requirements) { + if (!(StateImpl.OSGI_EE_NAMESPACE.equals(requirement.getNameSpace()) || requirement.isEffective())) + continue; + { + if (!resolveGenericReq(requirement, new ArrayList<ResolverBundle>(0))) { + if (DEBUG || DEBUG_GENERICS) + ResolverImpl.log("** GENERICS " + requirement.getVersionConstraint().getName() + "[" + requirement.getBundleDescription() + "] failed to resolve"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + state.addResolverError(requirement.getVersionConstraint().getBundle(), ResolverError.MISSING_GENERIC_CAPABILITY, requirement.getVersionConstraint().toString(), requirement.getVersionConstraint()); + if (!developmentMode) { + // fail fast; otherwise we want to attempt to resolver other constraints in dev mode + return false; + } + } else { + VersionSupplier supplier = requirement.getSelectedSupplier(); + Integer ee = supplier == null ? null : (Integer) ((GenericDescription) supplier.getBaseDescription()).getAttributes().get(ExportPackageDescriptionImpl.EQUINOX_EE); + if (ee != null && ((BundleDescriptionImpl) bundle.getBaseDescription()).getEquinoxEE() < 0) + ((BundleDescriptionImpl) bundle.getBundleDescription()).setEquinoxEE(ee); + } + } + } + return true; + } + + public synchronized void resolve(BundleDescription[] reRefresh, Dictionary<Object, Object>[] platformProperties) { + if (DEBUG) + ResolverImpl.log("*** BEGIN RESOLUTION ***"); //$NON-NLS-1$ + if (state == null) + throw new IllegalStateException("RESOLVER_NO_STATE"); //$NON-NLS-1$ + + if (!initialized) + initialize(); + hook = (state instanceof StateImpl) ? ((StateImpl) state).getResolverHook() : null; + try { + // set developmentMode each resolution + developmentMode = platformProperties.length == 0 ? false : StateImpl.DEVELOPMENT_MODE.equals(platformProperties[0].get(StateImpl.OSGI_RESOLVER_MODE)); + // set uses timeout each resolution + usesTimeout = getUsesTimeout(platformProperties); + // set limit for constraints with multiple suppliers each resolution + usesMultipleSuppliersLimit = getMultipleSuppliersLimit(platformProperties); + reRefresh = addDevConstraints(reRefresh); + // Unresolve all the supplied bundles and their dependents + if (reRefresh != null) + for (int i = 0; i < reRefresh.length; i++) { + ResolverBundle rb = bundleMapping.get(reRefresh[i]); + if (rb != null) + unresolveBundle(rb, false); + } + // reorder exports and bundles after unresolving the bundles + resolverExports.reorder(); + resolverBundles.reorder(); + reorderGenerics(); + // always get the latest EEs + getCurrentEEs(platformProperties); + boolean resolveOptional = platformProperties.length == 0 ? false : "true".equals(platformProperties[0].get("osgi.resolveOptional")); //$NON-NLS-1$//$NON-NLS-2$ + ResolverBundle[] currentlyResolved = null; + if (resolveOptional) { + BundleDescription[] resolvedBundles = state.getResolvedBundles(); + currentlyResolved = new ResolverBundle[resolvedBundles.length]; + for (int i = 0; i < resolvedBundles.length; i++) + currentlyResolved[i] = bundleMapping.get(resolvedBundles[i]); + } + // attempt to resolve all unresolved bundles + @SuppressWarnings("unchecked") + Collection<ResolverBundle> hookDisabled = Collections.EMPTY_LIST; + if (hook != null) { + List<ResolverBundle> resolvableBundles = new ArrayList<ResolverBundle>(unresolvedBundles); + List<BundleRevision> resolvableRevisions = new ArrayList<BundleRevision>(resolvableBundles.size()); + for (ResolverBundle bundle : resolvableBundles) + resolvableRevisions.add(bundle.getBundleDescription()); + ArrayMap<BundleRevision, ResolverBundle> resolvable = new ArrayMap<BundleRevision, ResolverBundle>(resolvableRevisions, resolvableBundles); + int size = resolvableBundles.size(); + hook.filterResolvable(resolvable); + if (resolvable.size() < size) { + hookDisabled = new ArrayList<ResolverBundle>(unresolvedBundles); + hookDisabled.removeAll(resolvableBundles); + } + } + + usesCalculationTimeout = false; + + List<ResolverBundle> toResolve = new ArrayList<ResolverBundle>(unresolvedBundles); + // first resolve the system bundle to allow osgi.ee capabilities to be resolved + List<ResolverBundle> unresolvedSystemBundles = new ArrayList<ResolverBundle>(1); + String systemBSN = getSystemBundle(); + for (Iterator<ResolverBundle> iToResolve = toResolve.iterator(); iToResolve.hasNext();) { + ResolverBundle rb = iToResolve.next(); + String symbolicName = rb.getName(); + if (symbolicName != null && symbolicName.equals(systemBSN)) { + unresolvedSystemBundles.add(rb); + iToResolve.remove(); + } + } + if (!unresolvedSystemBundles.isEmpty()) + resolveBundles(unresolvedSystemBundles.toArray(new ResolverBundle[unresolvedSystemBundles.size()]), platformProperties, hookDisabled); + + // Now resolve the rest + resolveBundles(toResolve.toArray(new ResolverBundle[toResolve.size()]), platformProperties, hookDisabled); + + @SuppressWarnings("unchecked") + Collection<ResolverBundle> optionalResolved = resolveOptional ? resolveOptionalConstraints(currentlyResolved) : Collections.EMPTY_LIST; + ResolverHook current = hook; + if (current != null) { + hook = null; + current.end(); + } + + // set the resolved status of the bundles in the State + // Note this must be done after calling end above in case end throws errors + stateResolveBundles(bundleMapping.values().toArray(new ResolverBundle[bundleMapping.size()])); + + for (ResolverBundle bundle : optionalResolved) { + state.resolveBundle(bundle.getBundleDescription(), false, null, null, null, null, null, null, null, null); + stateResolveBundle(bundle); + } + // reorder exports and bundles after resolving the bundles + resolverExports.reorder(); + resolverBundles.reorder(); + reorderGenerics(); + if (resolveOptional) + resolveOptionalConstraints(currentlyResolved); + if (DEBUG) + ResolverImpl.log("*** END RESOLUTION ***"); //$NON-NLS-1$ + } finally { + if (hook != null) + hook.end(); // need to make sure end is always called + hook = null; + } + } + + private long getUsesTimeout(Dictionary<Object, Object>[] platformProperties) { + try { + Object timeout = platformProperties.length == 0 ? null : platformProperties[0].get(USES_TIMEOUT_PROP); + if (timeout != null) { + long temp = Long.parseLong(timeout.toString()); + if (temp < 0) { + return -1; + } else if (temp == 0) { + return Long.MAX_VALUE; + } else { + return temp; + } + } + } catch (NumberFormatException e) { + // nothing; + } + return -1; + } + + private int getMultipleSuppliersLimit(Dictionary<Object, Object>[] platformProperties) { + try { + Object limit = platformProperties.length == 0 ? null : platformProperties[0].get(MULTIPLE_SUPPLIERS_LIMIT_PROP); + if (limit != null) { + int temp = Integer.parseInt(limit.toString()); + if (temp < 0) { + return MAX_MULTIPLE_SUPPLIERS_MERGE; + } else if (temp == 0) { + return Integer.MAX_VALUE; + } else { + return temp; + } + } + } catch (NumberFormatException e) { + // nothing; + } + return MAX_MULTIPLE_SUPPLIERS_MERGE; + } + + private BundleDescription[] addDevConstraints(BundleDescription[] reRefresh) { + if (!developmentMode) + return reRefresh; // we don't care about this unless we are in development mode + // when in develoment mode we need to reRefresh hosts of unresolved fragments that add new constraints + // and reRefresh and unresolved bundles that have dependents + Set<BundleDescription> additionalRefresh = new HashSet<BundleDescription>(); + ResolverBundle[] unresolved = unresolvedBundles.toArray(new ResolverBundle[unresolvedBundles.size()]); + for (int i = 0; i < unresolved.length; i++) { + addUnresolvedWithDependents(unresolved[i], additionalRefresh); + addHostsFromFragmentConstraints(unresolved[i], additionalRefresh); + } + if (additionalRefresh.size() == 0) + return reRefresh; // no new bundles found to refresh + // add the original reRefresh bundles to the set + if (reRefresh != null) + for (int i = 0; i < reRefresh.length; i++) + additionalRefresh.add(reRefresh[i]); + return additionalRefresh.toArray(new BundleDescription[additionalRefresh.size()]); + } + + private void addUnresolvedWithDependents(ResolverBundle unresolved, Set<BundleDescription> additionalRefresh) { + BundleDescription[] dependents = unresolved.getBundleDescription().getDependents(); + if (dependents.length > 0) + additionalRefresh.add(unresolved.getBundleDescription()); + } + + private void addHostsFromFragmentConstraints(ResolverBundle unresolved, Set<BundleDescription> additionalRefresh) { + if (!unresolved.isFragment()) + return; + ImportPackageSpecification[] newImports = unresolved.getBundleDescription().getImportPackages(); + BundleSpecification[] newRequires = unresolved.getBundleDescription().getRequiredBundles(); + if (newImports.length == 0 && newRequires.length == 0) + return; // the fragment does not have its own constraints + BundleConstraint hostConstraint = unresolved.getHost(); + List<ResolverBundle> hosts = resolverBundles.get(hostConstraint.getVersionConstraint().getName()); + for (ResolverBundle host : hosts) + if (hostConstraint.isSatisfiedBy(host) && host.isResolved()) + // we found a host that is resolved; + // add it to the set of bundle to refresh so we can ensure this fragment is allowed to resolve + additionalRefresh.add(host.getBundleDescription()); + + } + + private Collection<ResolverBundle> resolveOptionalConstraints(ResolverBundle[] bundles) { + Collection<ResolverBundle> result = new ArrayList<ResolverBundle>(); + for (int i = 0; i < bundles.length; i++) { + if (bundles[i] != null && resolveOptionalConstraints(bundles[i])) { + result.add(bundles[i]); + } + } + return result; + } + + // TODO this does not do proper uses constraint verification. + private boolean resolveOptionalConstraints(ResolverBundle bundle) { + BundleConstraint[] requires = bundle.getRequires(); + List<ResolverBundle> cycle = new ArrayList<ResolverBundle>(); + boolean resolvedOptional = false; + for (int i = 0; i < requires.length; i++) + if (requires[i].isOptional() && requires[i].getSelectedSupplier() == null) { + cycle.clear(); + resolveRequire(requires[i], cycle); + 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].getSelectedSupplier() == null) { + cycle.clear(); + resolveImport(imports[i], cycle); + if (imports[i].getSelectedSupplier() != null) + resolvedOptional = true; + } + return resolvedOptional; + } + + private void getCurrentEEs(Dictionary<Object, Object>[] platformProperties) { + CURRENT_EES = new String[platformProperties.length][]; + for (int i = 0; i < platformProperties.length; i++) { + String eeSpecs = (String) platformProperties[i].get(Constants.FRAMEWORK_EXECUTIONENVIRONMENT); + CURRENT_EES[i] = ManifestElement.getArrayFromList(eeSpecs, ","); //$NON-NLS-1$ + } + } + + private void resolveBundles(ResolverBundle[] bundles, Dictionary<Object, Object>[] platformProperties, Collection<ResolverBundle> hookDisabled) { + + // First check that all the meta-data is valid for each unresolved bundle + // This will reset the resolvable flag for each bundle + for (ResolverBundle bundle : bundles) { + state.removeResolverErrors(bundle.getBundleDescription()); + // if in development mode then make all bundles resolvable + // we still want to call isResolvable here to populate any possible ResolverErrors for the bundle + bundle.setResolvable(isResolvable(bundle, platformProperties, hookDisabled) || developmentMode); + } + selectSingletons(bundles); + resolveBundles0(bundles, platformProperties); + if (DEBUG_WIRING) + printWirings(); + } + + private void selectSingletons(ResolverBundle[] bundles) { + if (developmentMode) + return; // want all singletons to resolve in devmode + Map<String, Collection<ResolverBundle>> selectedSingletons = new HashMap<String, Collection<ResolverBundle>>(bundles.length); + for (ResolverBundle bundle : bundles) { + if (!bundle.getBundleDescription().isSingleton() || !bundle.isResolvable()) + continue; + String bsn = bundle.getName(); + Collection<ResolverBundle> selected = selectedSingletons.get(bsn); + if (selected != null) + continue; // already processed the bsn + selected = new ArrayList<ResolverBundle>(1); + selectedSingletons.put(bsn, selected); + + List<ResolverBundle> sameBSN = resolverBundles.get(bsn); + if (sameBSN.size() < 2) { + selected.add(bundle); + continue; + } + // prime selected with resolved singleton bundles + for (ResolverBundle singleton : sameBSN) { + if (singleton.getBundleDescription().isSingleton() && singleton.getBundleDescription().isResolved()) + selected.add(singleton); + } + // get the collision map for the BSN + Map<ResolverBundle, Collection<ResolverBundle>> collisionMap = getCollisionMap(sameBSN); + // process the collision map + for (ResolverBundle singleton : sameBSN) { + if (selected.contains(singleton)) + continue; // no need to process resolved bundles + Collection<ResolverBundle> collisions = collisionMap.get(singleton); + if (collisions == null || !singleton.isResolvable()) + continue; // not a singleton or not resolvable + Collection<ResolverBundle> pickOneToResolve = new ArrayList<ResolverBundle>(); + for (ResolverBundle collision : collisions) { + if (selected.contains(collision)) { + // Must fail since there is already a selected bundle which is a collision of the singleton bundle + singleton.setResolvable(false); + state.addResolverError(singleton.getBundleDescription(), ResolverError.SINGLETON_SELECTION, collision.getBundleDescription().toString(), null); + break; + } + if (!pickOneToResolve.contains(collision)) + pickOneToResolve.add(collision); + } + // need to make sure the bundle does not collide from the POV of another entry + for (Map.Entry<ResolverBundle, Collection<ResolverBundle>> collisionEntry : collisionMap.entrySet()) { + if (collisionEntry.getKey() != singleton && collisionEntry.getValue().contains(singleton)) { + if (selected.contains(collisionEntry.getKey())) { + // Must fail since there is already a selected bundle for which the singleton bundle is a collision + singleton.setResolvable(false); + state.addResolverError(singleton.getBundleDescription(), ResolverError.SINGLETON_SELECTION, collisionEntry.getKey().getBundleDescription().toString(), null); + break; + } + if (!pickOneToResolve.contains(collisionEntry.getKey())) + pickOneToResolve.add(collisionEntry.getKey()); + } + } + if (singleton.isResolvable()) { + pickOneToResolve.add(singleton); + selected.add(pickOneToResolve(pickOneToResolve)); + } + } + } + } + + private ResolverBundle pickOneToResolve(Collection<ResolverBundle> pickOneToResolve) { + ResolverBundle selectedVersion = null; + for (ResolverBundle singleton : pickOneToResolve) { + if (selectedVersion == null) + selectedVersion = singleton; + boolean higherVersion = selectionPolicy != null ? selectionPolicy.compare(selectedVersion.getBundleDescription(), singleton.getBundleDescription()) > 0 : selectedVersion.getVersion().compareTo(singleton.getVersion()) < 0; + if (higherVersion) + selectedVersion = singleton; + } + + for (ResolverBundle singleton : pickOneToResolve) { + if (singleton != selectedVersion) { + singleton.setResolvable(false); + state.addResolverError(singleton.getBundleDescription(), ResolverError.SINGLETON_SELECTION, selectedVersion.getBundleDescription().toString(), null); + } + } + return selectedVersion; + } + + private Map<ResolverBundle, Collection<ResolverBundle>> getCollisionMap(List<ResolverBundle> sameBSN) { + Map<ResolverBundle, Collection<ResolverBundle>> result = new HashMap<ResolverBundle, Collection<ResolverBundle>>(); + for (ResolverBundle singleton : sameBSN) { + if (!singleton.getBundleDescription().isSingleton() || !singleton.isResolvable()) + continue; // ignore non-singleton and non-resolvable + List<ResolverBundle> collisionCandidates = new ArrayList<ResolverBundle>(sameBSN.size() - 1); + List<BundleCapability> capabilities = new ArrayList<BundleCapability>(sameBSN.size() - 1); + for (ResolverBundle collision : sameBSN) { + if (collision == singleton || !collision.getBundleDescription().isSingleton() || !collision.isResolvable()) + continue; // Ignore the bundle we are checking and non-singletons and non-resolvable + collisionCandidates.add(collision); + capabilities.add(getIdentity(collision)); + } + if (hook != null) + hook.filterSingletonCollisions(getIdentity(singleton), asCapabilities(new ArrayMap<BundleCapability, ResolverBundle>(capabilities, collisionCandidates))); + result.put(singleton, collisionCandidates); + } + return result; + } + + private BundleCapability getIdentity(ResolverBundle bundle) { + List<BundleCapability> identities = bundle.getBundleDescription().getDeclaredCapabilities(IdentityNamespace.IDENTITY_NAMESPACE); + return identities.size() == 1 ? identities.get(0) : bundle.getCapability(); + } + + private void resolveBundles0(ResolverBundle[] bundles, Dictionary<Object, Object>[] platformProperties) { + if (developmentMode) + // need to sort bundles to keep consistent order for fragment attachment (bug 174930) + Arrays.sort(bundles); + // First attach all fragments to the matching hosts + Collection<String> processedFragments = new HashSet<String>(bundles.length); + for (int i = 0; i < bundles.length; i++) + attachFragment(bundles[i], processedFragments); + + // Lists of cyclic dependencies recording during resolving + List<ResolverBundle> cycle = new ArrayList<ResolverBundle>(1); // start small + // Attempt to resolve all unresolved bundles + for (int i = 0; i < bundles.length; i++) { + if (DEBUG) + ResolverImpl.log("** RESOLVING " + bundles[i] + " **"); //$NON-NLS-1$ //$NON-NLS-2$ + cycle.clear(); + resolveBundle(bundles[i], cycle); + // 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); + } + // Resolve all fragments that are still attached to at least one host. + if (unresolvedBundles.size() > 0) { + ResolverBundle[] unresolved = unresolvedBundles.toArray(new ResolverBundle[unresolvedBundles.size()]); + for (int i = 0; i < unresolved.length; i++) + resolveFragment(unresolved[i]); + } + checkUsesConstraints(bundles, platformProperties); + checkComposites(bundles, platformProperties); + } + + private void checkComposites(ResolverBundle[] bundles, Dictionary<Object, Object>[] platformProperties) { + CompositeResolveHelperRegistry helpers = getCompositeHelpers(); + if (helpers == null) + return; + Set<ResolverBundle> exclude = null; + for (int i = 0; i < bundles.length; i++) { + CompositeResolveHelper helper = helpers.getCompositeResolveHelper(bundles[i].getBundleDescription()); + if (helper == null) + continue; + if (!bundles[i].isResolved()) + continue; + if (!helper.giveExports(getExportsWiredTo(bundles[i], null))) { + state.addResolverError(bundles[i].getBundleDescription(), ResolverError.DISABLED_BUNDLE, null, null); + bundles[i].setResolvable(false); + // We pass false for keepFragmentsAttached because we need to redo the attachments (bug 272561) + setBundleUnresolved(bundles[i], false, false); + if (exclude == null) + exclude = new HashSet<ResolverBundle>(1); + exclude.add(bundles[i]); + } + } + reResolveBundles(exclude, bundles, platformProperties); + } + + private void checkUsesConstraints(ResolverBundle[] bundles, Dictionary<Object, Object>[] platformProperties) { + List<ResolverConstraint> conflictingConstraints = findBestCombination(bundles, platformProperties); + if (conflictingConstraints == null) + return; + Set<ResolverBundle> conflictedBundles = null; + for (ResolverConstraint conflict : conflictingConstraints) { + if (conflict.isOptional()) { + conflict.clearPossibleSuppliers(); + continue; + } + if (conflictedBundles == null) + conflictedBundles = new HashSet<ResolverBundle>(conflictingConstraints.size()); + ResolverBundle conflictedBundle; + if (conflict.isFromFragment()) + conflictedBundle = bundleMapping.get(conflict.getVersionConstraint().getBundle()); + else + conflictedBundle = conflict.getBundle(); + if (conflictedBundle != null) { + if (DEBUG_USES) + System.out.println("Found conflicting constraint: " + conflict + " in bundle " + conflictedBundle); //$NON-NLS-1$//$NON-NLS-2$ + conflictedBundles.add(conflictedBundle); + int type = conflict instanceof ResolverImport ? ResolverError.IMPORT_PACKAGE_USES_CONFLICT : ResolverError.REQUIRE_BUNDLE_USES_CONFLICT; + state.addResolverError(conflictedBundle.getBundleDescription(), type, conflict.getVersionConstraint().toString(), conflict.getVersionConstraint()); + conflictedBundle.setResolvable(false); + // We pass false for keepFragmentsAttached because we need to redo the attachments (bug 272561) + setBundleUnresolved(conflictedBundle, false, false); + } + } + reResolveBundles(conflictedBundles, bundles, platformProperties); + } + + private void reResolveBundles(Set<ResolverBundle> exclude, ResolverBundle[] bundles, Dictionary<Object, Object>[] platformProperties) { + if (exclude == null || exclude.size() == 0) + return; + List<ResolverBundle> remainingUnresolved = new ArrayList<ResolverBundle>(); + for (int i = 0; i < bundles.length; i++) { + if (!exclude.contains(bundles[i])) { + // We pass false for keepFragmentsAttached because we need to redo the attachments (bug 272561) + setBundleUnresolved(bundles[i], false, false); + remainingUnresolved.add(bundles[i]); + } + } + resolveBundles0(remainingUnresolved.toArray(new ResolverBundle[remainingUnresolved.size()]), platformProperties); + } + + private List<ResolverConstraint> findBestCombination(ResolverBundle[] bundles, Dictionary<Object, Object>[] platformProperties) { + Object usesMode = platformProperties.length == 0 ? null : platformProperties[0].get("osgi.resolver.usesMode"); //$NON-NLS-1$ + if (usesMode == null) + usesMode = secureAction.getProperty("osgi.resolver.usesMode"); //$NON-NLS-1$ + if ("ignore".equals(usesMode) || developmentMode) //$NON-NLS-1$ + return null; + Set<String> bundleConstraints = new HashSet<String>(); + Set<String> packageConstraints = new HashSet<String>(); + // first try out the initial selections + List<ResolverConstraint> initialConflicts = getConflicts(bundles, packageConstraints, bundleConstraints); + if (initialConflicts == null || "tryFirst".equals(usesMode) || usesCalculationTimeout) { //$NON-NLS-1$ + groupingChecker.clear(); + // the first combination have no conflicts or + // we only are trying the first combination or + // we have timed out the calculation; return without iterating over all combinations + return initialConflicts; + } + ResolverConstraint[][] multipleSuppliers = getMultipleSuppliers(bundles, packageConstraints, bundleConstraints); + List<ResolverConstraint> conflicts = null; + int[] bestCombination = new int[multipleSuppliers.length]; + conflicts = findBestCombination(bundles, multipleSuppliers, bestCombination, initialConflicts); + if (DEBUG_USES) { + System.out.print("Best combination found: "); //$NON-NLS-1$ + printCombination(bestCombination); + } + for (int i = 0; i < bestCombination.length; i++) { + for (int j = 0; j < multipleSuppliers[i].length; j++) { + ResolverConstraint constraint = multipleSuppliers[i][j]; + constraint.setSelectedSupplier(bestCombination[i]); + // sanity check to make sure we did not just get wired to our own dropped export + VersionSupplier selectedSupplier = constraint.getSelectedSupplier(); + if (selectedSupplier != null) + selectedSupplier.setSubstitute(null); + } + } + + // do not need to keep uses data in memory + groupingChecker.clear(); + return conflicts; + } + + private int[] getCombination(ResolverConstraint[][] multipleSuppliers, int[] combination) { + for (int i = 0; i < combination.length; i++) + combination[i] = multipleSuppliers[i][0].getSelectedSupplierIndex(); + return combination; + } + + private List<ResolverConstraint> findBestCombination(ResolverBundle[] bundles, ResolverConstraint[][] multipleSuppliers, int[] bestCombination, List<ResolverConstraint> bestConflicts) { + // 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 + long initialTime = System.currentTimeMillis(); + long timeLimit; + if (usesTimeout < 0) + timeLimit = Math.min(MAX_USES_TIME_BASE + (bundles.length * 30), MAX_USES_TIME_LIMIT); + else + timeLimit = usesTimeout; + + int bestConflictCount = getConflictCount(bestConflicts); + ResolverBundle[] bestConflictBundles = getConflictedBundles(bestConflicts); + while (bestConflictCount != 0 && getNextCombination(multipleSuppliers)) { + if ((System.currentTimeMillis() - initialTime) > timeLimit) { + if (DEBUG_USES) + System.out.println("Uses constraint check has timedout. Using the best solution found so far."); //$NON-NLS-1$ + usesCalculationTimeout = true; + break; + } + if (DEBUG_USES) + printCombination(getCombination(multipleSuppliers, new int[multipleSuppliers.length])); + // first count the conflicts for the bundles with conflicts from the best combination + // this significantly reduces the time it takes to populate the GroupingChecker for cases where + // the combination is no better. + List<ResolverConstraint> conflicts = getConflicts(bestConflictBundles, null, null); + int conflictCount = getConflictCount(conflicts); + if (conflictCount >= bestConflictCount) { + if (DEBUG_USES) + System.out.println("Combination is not better that current best: " + conflictCount + ">=" + bestConflictCount); //$NON-NLS-1$ //$NON-NLS-2$ + // no need to test the other bundles; + // this combination is no better for the bundles which conflict with the current best combination + continue; + } + // this combination improves upon the conflicts for the bundles which conflict with the current best combination; + // do an complete conflict count + conflicts = getConflicts(bundles, null, null); + conflictCount = getConflictCount(conflicts); + if (conflictCount < bestConflictCount) { + // this combination is better that the current best combination; save this combination as the current best + bestConflictCount = conflictCount; + bestConflicts = conflicts; + getCombination(multipleSuppliers, bestCombination); + bestConflictBundles = getConflictedBundles(bestConflicts); + if (DEBUG_USES) + System.out.println("Combination selected as current best: number of conflicts: " + bestConflictCount); //$NON-NLS-1$ + } else if (DEBUG_USES) { + System.out.println("Combination is not better that current best: " + conflictCount + ">=" + bestConflictCount); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + return bestConflicts; + } + + private void printCombination(int[] curCombination) { + StringBuffer sb = new StringBuffer(); + sb.append('['); + for (int i = 0; i < curCombination.length; i++) { + sb.append(curCombination[i]); + if (i < curCombination.length - 1) + sb.append(','); + } + sb.append(']'); + System.out.println(sb.toString()); + } + + private ResolverBundle[] getConflictedBundles(List<ResolverConstraint> bestConflicts) { + if (bestConflicts == null) + return new ResolverBundle[0]; + List<ResolverBundle> conflictedBundles = new ArrayList<ResolverBundle>(bestConflicts.size()); + for (ResolverConstraint constraint : bestConflicts) + if (!conflictedBundles.contains(constraint.getBundle())) + conflictedBundles.add(constraint.getBundle()); + return conflictedBundles.toArray(new ResolverBundle[conflictedBundles.size()]); + } + + private boolean getNextCombination(ResolverConstraint[][] multipleSuppliers) { + int current = 0; + while (current < multipleSuppliers.length) { + if (multipleSuppliers[current][0].selectNextSupplier()) { + for (int i = 1; i < multipleSuppliers[current].length; i++) + multipleSuppliers[current][i].selectNextSupplier(); + return true; // the current slot has a next supplier + } + for (int i = 0; i < multipleSuppliers[current].length; i++) + multipleSuppliers[current][i].setSelectedSupplier(0); // reset the current slot + current++; // move to the next slot + } + return false; + } + + // only count non-optional conflicts + private int getConflictCount(List<ResolverConstraint> conflicts) { + if (conflicts == null || conflicts.size() == 0) + return 0; + int result = 0; + for (ResolverConstraint constraint : conflicts) + if (!constraint.isOptional()) + result += 1; + return result; + } + + private List<ResolverConstraint> getConflicts(ResolverBundle[] bundles, Set<String> packageConstraints, Set<String> bundleConstraints) { + groupingChecker.clear(); + List<ResolverConstraint> conflicts = null; + for (int i = 0; i < bundles.length; i++) + conflicts = addConflicts(bundles[i], packageConstraints, bundleConstraints, conflicts); + return conflicts; + } + + private List<ResolverConstraint> addConflicts(ResolverBundle bundle, Set<String> packageConstraints, Set<String> bundleConstraints, List<ResolverConstraint> conflicts) { + BundleConstraint[] requires = bundle.getRequires(); + for (int i = 0; i < requires.length; i++) { + ResolverBundle selectedSupplier = (ResolverBundle) requires[i].getSelectedSupplier(); + PackageRoots[][] conflict = selectedSupplier == null ? null : groupingChecker.isConsistent(bundle, selectedSupplier); + if (conflict != null) { + addConflictNames(conflict, packageConstraints, bundleConstraints); + if (conflicts == null) + conflicts = new ArrayList<ResolverConstraint>(1); + conflicts.add(requires[i]); + } + } + ResolverImport[] imports = bundle.getImportPackages(); + for (int i = 0; i < imports.length; i++) { + ResolverExport selectedSupplier = (ResolverExport) imports[i].getSelectedSupplier(); + PackageRoots[][] conflict = selectedSupplier == null ? null : groupingChecker.isConsistent(bundle, selectedSupplier); + if (conflict != null) { + addConflictNames(conflict, packageConstraints, bundleConstraints); + if (conflicts == null) + conflicts = new ArrayList<ResolverConstraint>(1); + conflicts.add(imports[i]); + } + } + + GenericConstraint[] genericRequires = bundle.getGenericRequires(); + for (GenericConstraint capabilityRequirement : genericRequires) { + VersionSupplier[] suppliers = capabilityRequirement.getMatchingCapabilities(); + if (suppliers == null) + continue; + for (VersionSupplier supplier : suppliers) { + PackageRoots[][] conflict = groupingChecker.isConsistent(bundle, (GenericCapability) supplier); + if (conflict != null) { + addConflictNames(conflict, packageConstraints, bundleConstraints); + if (conflicts == null) + conflicts = new ArrayList<ResolverConstraint>(1); + conflicts.add(capabilityRequirement); + } + } + } + return conflicts; + } + + // records the conflict names we can use to scope down the list of multiple suppliers + private void addConflictNames(PackageRoots[][] conflict, Set<String> packageConstraints, Set<String> bundleConstraints) { + if (packageConstraints == null || bundleConstraints == null) + return; + for (int i = 0; i < conflict.length; i++) { + packageConstraints.add(conflict[i][0].getName()); + packageConstraints.add(conflict[i][1].getName()); + ResolverExport[] exports0 = conflict[i][0].getRoots(); + if (exports0 != null) + for (int j = 0; j < exports0.length; j++) { + ResolverBundle exporter = exports0[j].getExporter(); + if (exporter != null && exporter.getName() != null) + bundleConstraints.add(exporter.getName()); + } + ResolverExport[] exports1 = conflict[i][1].getRoots(); + if (exports1 != null) + for (int j = 0; j < exports1.length; j++) { + ResolverBundle exporter = exports1[j].getExporter(); + if (exporter != null && exporter.getName() != null) + bundleConstraints.add(exporter.getName()); + } + } + } + + // get a list of resolver constraints that have multiple suppliers + // a 2 demensional array is used each entry is a list of identical constraints that have identical suppliers. + private ResolverConstraint[][] getMultipleSuppliers(ResolverBundle[] bundles, Set<String> packageConstraints, Set<String> bundleConstraints) { + List<ResolverImport> multipleImportSupplierList = new ArrayList<ResolverImport>(1); + List<BundleConstraint> multipleRequireSupplierList = new ArrayList<BundleConstraint>(1); + List<GenericConstraint> multipleGenericSupplierList = new ArrayList<GenericConstraint>(1); + for (ResolverBundle bundle : bundles) { + BundleConstraint[] requires = bundle.getRequires(); + for (BundleConstraint require : requires) + if (require.getNumPossibleSuppliers() > 1) + multipleRequireSupplierList.add(require); + ResolverImport[] imports = bundle.getImportPackages(); + for (ResolverImport importPkg : imports) { + if (importPkg.getNumPossibleSuppliers() > 1) { + Integer eeProfile = (Integer) ((ResolverExport) importPkg.getSelectedSupplier()).getExportPackageDescription().getDirective(ExportPackageDescriptionImpl.EQUINOX_EE); + if (eeProfile.intValue() < 0) { + // this is a normal package; always add it + multipleImportSupplierList.add(importPkg); + } else { + // this is a system bundle export + // If other exporters of this package also require the system bundle + // then this package does not need to be added to the mix + // this is an optimization for bundles like org.eclipse.xerces + // that export lots of packages also exported by the system bundle on J2SE 1.4 + VersionSupplier[] suppliers = importPkg.getPossibleSuppliers(); + for (int suppliersIndex = 1; suppliersIndex < suppliers.length; suppliersIndex++) { + Integer ee = (Integer) ((ResolverExport) suppliers[suppliersIndex]).getExportPackageDescription().getDirective(ExportPackageDescriptionImpl.EQUINOX_EE); + if (ee.intValue() >= 0) + continue; + if (((ResolverExport) suppliers[suppliersIndex]).getExporter().getRequire(getSystemBundle()) == null) + if (((ResolverExport) suppliers[suppliersIndex]).getExporter().getRequire(Constants.SYSTEM_BUNDLE_SYMBOLICNAME) == null) { + multipleImportSupplierList.add(importPkg); + break; + } + } + } + } + } + GenericConstraint[] genericRequires = bundle.getGenericRequires(); + for (GenericConstraint genericRequire : genericRequires) + if (genericRequire.getNumPossibleSuppliers() > 1 && genericRequire.supplierHasUses()) + multipleGenericSupplierList.add(genericRequire); + } + List<ResolverConstraint[]> results = new ArrayList<ResolverConstraint[]>(); + if (multipleImportSupplierList.size() + multipleRequireSupplierList.size() + multipleGenericSupplierList.size() > usesMultipleSuppliersLimit) { + // we have hit a max on the multiple suppliers in the lists without merging. + // first merge the identical constraints that have identical suppliers + Map<String, List<List<ResolverConstraint>>> multipleImportSupplierMaps = new HashMap<String, List<List<ResolverConstraint>>>(); + for (ResolverImport importPkg : multipleImportSupplierList) + addMutipleSupplierConstraint(multipleImportSupplierMaps, importPkg, importPkg.getName()); + Map<String, List<List<ResolverConstraint>>> multipleRequireSupplierMaps = new HashMap<String, List<List<ResolverConstraint>>>(); + for (BundleConstraint requireBundle : multipleRequireSupplierList) + addMutipleSupplierConstraint(multipleRequireSupplierMaps, requireBundle, requireBundle.getName()); + Map<String, List<List<ResolverConstraint>>> multipleGenericSupplierMaps = new HashMap<String, List<List<ResolverConstraint>>>(); + for (GenericConstraint genericRequire : multipleGenericSupplierList) + addMutipleSupplierConstraint(multipleGenericSupplierMaps, genericRequire, genericRequire.getNameSpace()); + addMergedSuppliers(results, multipleImportSupplierMaps); + addMergedSuppliers(results, multipleRequireSupplierMaps); + addMergedSuppliers(results, multipleGenericSupplierMaps); + // check the results to see if we have reduced the number enough + if (results.size() > usesMultipleSuppliersLimit && packageConstraints != null && bundleConstraints != null) { + // we still have too big of a list; filter out constraints that are not in conflict + List<ResolverConstraint[]> tooBig = results; + results = new ArrayList<ResolverConstraint[]>(); + for (ResolverConstraint[] constraints : tooBig) { + ResolverConstraint constraint = constraints.length > 0 ? constraints[0] : null; + if (constraint instanceof ResolverImport) { + if (packageConstraints.contains(constraint.getName())) + results.add(constraints); + } else if (constraint instanceof BundleConstraint) { + if (bundleConstraints.contains(constraint.getName())) + results.add(constraints); + } + } + } + } else { + // the size is acceptable; just copy the lists as-is + for (ResolverConstraint constraint : multipleImportSupplierList) + results.add(new ResolverConstraint[] {constraint}); + for (ResolverConstraint constraint : multipleRequireSupplierList) + results.add(new ResolverConstraint[] {constraint}); + for (ResolverConstraint constraint : multipleGenericSupplierList) + results.add(new ResolverConstraint[] {constraint}); + + } + return results.toArray(new ResolverConstraint[results.size()][]); + } + + String getSystemBundle() { + Dictionary<?, ?>[] platformProperties = state.getPlatformProperties(); + String systemBundle = platformProperties.length == 0 ? null : (String) platformProperties[0].get(StateImpl.STATE_SYSTEM_BUNDLE); + if (systemBundle == null) + systemBundle = EquinoxContainer.NAME; + return systemBundle; + } + + private void addMergedSuppliers(List<ResolverConstraint[]> mergedSuppliers, Map<String, List<List<ResolverConstraint>>> constraints) { + for (List<List<ResolverConstraint>> mergedConstraintLists : constraints.values()) { + for (List<ResolverConstraint> constraintList : mergedConstraintLists) { + mergedSuppliers.add(constraintList.toArray(new ResolverConstraint[constraintList.size()])); + } + } + } + + private void addMutipleSupplierConstraint(Map<String, List<List<ResolverConstraint>>> constraints, ResolverConstraint constraint, String key) { + List<List<ResolverConstraint>> mergedConstraintLists = constraints.get(key); + if (mergedConstraintLists == null) { + mergedConstraintLists = new ArrayList<List<ResolverConstraint>>(0); + List<ResolverConstraint> constraintList = new ArrayList<ResolverConstraint>(1); + constraintList.add(constraint); + mergedConstraintLists.add(constraintList); + constraints.put(key, mergedConstraintLists); + return; + } + for (List<ResolverConstraint> constraintList : mergedConstraintLists) { + ResolverConstraint mergedConstraint = constraintList.get(0); + VersionSupplier[] suppliers1 = constraint.getPossibleSuppliers(); + VersionSupplier[] suppliers2 = mergedConstraint.getPossibleSuppliers(); + if (suppliers1.length != suppliers2.length) + continue; + for (int i = 0; i < suppliers1.length; i++) + if (suppliers1[i] != suppliers2[i]) + continue; + constraintList.add(constraint); + return; + } + List<ResolverConstraint> constraintList = new ArrayList<ResolverConstraint>(1); + constraintList.add(constraint); + mergedConstraintLists.add(constraintList); + } + + private void checkCycle(List<ResolverBundle> cycle) { + int cycleSize = cycle.size(); + if (cycleSize == 0) + return; + cycleLoop: for (Iterator<ResolverBundle> iCycle = cycle.iterator(); iCycle.hasNext();) { + ResolverBundle cycleBundle = iCycle.next(); + if (!cycleBundle.isResolvable()) { + iCycle.remove(); // remove this bundle from the list of bundles that need re-resolved + continue cycleLoop; + } + // Check that we haven't wired to any dropped exports + ResolverImport[] imports = cycleBundle.getImportPackages(); + for (int j = 0; j < imports.length; j++) { + // check for dropped exports + while (imports[j].getSelectedSupplier() != null) { + ResolverExport importSupplier = (ResolverExport) imports[j].getSelectedSupplier(); + if (importSupplier.getSubstitute() != null) + imports[j].selectNextSupplier(); + else + break; + } + if (!imports[j].isDynamic() && !imports[j].isOptional() && imports[j].getSelectedSupplier() == null) { + cycleBundle.setResolvable(false); + state.addResolverError(imports[j].getVersionConstraint().getBundle(), ResolverError.MISSING_IMPORT_PACKAGE, imports[j].getVersionConstraint().toString(), imports[j].getVersionConstraint()); + iCycle.remove(); + continue cycleLoop; + } + } + } + if (cycle.size() != cycleSize) { + //we removed an un-resolvable bundle; must re-resolve remaining cycle + for (int i = 0; i < cycle.size(); i++) { + ResolverBundle cycleBundle = cycle.get(i); + cycleBundle.clearWires(); + } + List<ResolverBundle> innerCycle = new ArrayList<ResolverBundle>(cycle.size()); + for (int i = 0; i < cycle.size(); i++) + resolveBundle(cycle.get(i), innerCycle); + checkCycle(innerCycle); + } else { + for (int i = 0; i < cycle.size(); i++) { + if (DEBUG || DEBUG_CYCLES) + ResolverImpl.log("Pushing " + cycle.get(i) + " to RESOLVED"); //$NON-NLS-1$ //$NON-NLS-2$ + setBundleResolved(cycle.get(i)); + } + } + } + + @SuppressWarnings("unchecked") + static Collection<BundleCapability> asCapabilities(Collection<? extends BundleCapability> capabilities) { + return (Collection<BundleCapability>) capabilities; + } + + private void resolveFragment(ResolverBundle fragment) { + if (!fragment.isFragment()) + return; + if (fragment.getHost().getNumPossibleSuppliers() > 0) + if (!developmentMode || state.getResolverErrors(fragment.getBundleDescription()).length == 0) + setBundleResolved(fragment); + } + + // This method will attempt to resolve the supplied bundle and any bundles that it is dependent on + private boolean resolveBundle(ResolverBundle bundle, List<ResolverBundle> cycle) { + if (bundle.isFragment()) + return false; + if (!bundle.isResolvable()) { + if (DEBUG) + ResolverImpl.log(" - " + bundle + " is unresolvable"); //$NON-NLS-1$ //$NON-NLS-2$ + return false; + } + switch (bundle.getState()) { + case ResolverBundle.RESOLVED : + // 'bundle' is already resolved so just return + if (DEBUG) + ResolverImpl.log(" - " + bundle + " already resolved"); //$NON-NLS-1$ //$NON-NLS-2$ + return true; + case ResolverBundle.UNRESOLVED : + // 'bundle' is UNRESOLVED so move to RESOLVING + bundle.clearWires(); + setBundleResolving(bundle); + break; + case ResolverBundle.RESOLVING : + if (cycle.contains(bundle)) + return true; + break; + default : + break; + } + + boolean failed = false; + + if (!failed) { + GenericConstraint[] genericRequires = bundle.getGenericRequires(); + for (int i = 0; i < genericRequires.length; i++) { + if (genericRequires[i].isEffective()) { + if (!resolveGenericReq(genericRequires[i], cycle)) { + if (DEBUG || DEBUG_GENERICS) + ResolverImpl.log("** GENERICS " + genericRequires[i].getVersionConstraint().getName() + "[" + genericRequires[i].getBundleDescription() + "] failed to resolve"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + state.addResolverError(genericRequires[i].getVersionConstraint().getBundle(), ResolverError.MISSING_GENERIC_CAPABILITY, genericRequires[i].getVersionConstraint().toString(), genericRequires[i].getVersionConstraint()); + if (genericRequires[i].isFromFragment()) { + if (!developmentMode) // only detach fragments when not in devmode + bundle.detachFragment(bundleMapping.get(genericRequires[i].getVersionConstraint().getBundle()), null); + continue; + } + if (!developmentMode) { + // fail fast; otherwise we want to attempt to resolver other constraints in dev mode + failed = true; + break; + } + } else { + if (StateImpl.OSGI_EE_NAMESPACE.equals(genericRequires[i].getNameSpace())) { + VersionSupplier supplier = genericRequires[i].getSelectedSupplier(); + Integer ee = supplier == null ? null : (Integer) ((GenericDescription) supplier.getBaseDescription()).getAttributes().get(ExportPackageDescriptionImpl.EQUINOX_EE); + if (ee != null && ((BundleDescriptionImpl) bundle.getBaseDescription()).getEquinoxEE() < 0) + ((BundleDescriptionImpl) bundle.getBundleDescription()).setEquinoxEE(ee); + } + } + } + } + } + + if (!failed) { + // Iterate thru required bundles of 'bundle' trying to find matching bundles. + BundleConstraint[] requires = bundle.getRequires(); + for (int i = 0; i < requires.length; i++) { + if (!resolveRequire(requires[i], cycle)) { + if (DEBUG || DEBUG_REQUIRES) + ResolverImpl.log("** REQUIRE " + requires[i].getVersionConstraint().getName() + "[" + requires[i].getBundleDescription() + "] failed to resolve"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + state.addResolverError(requires[i].getVersionConstraint().getBundle(), ResolverError.MISSING_REQUIRE_BUNDLE, requires[i].getVersionConstraint().toString(), requires[i].getVersionConstraint()); + // If the require has failed to resolve and it is from a fragment, then remove the fragment from the host + if (requires[i].isFromFragment()) { + if (!developmentMode) // only detach fragments when not in devmode + bundle.detachFragment(bundleMapping.get(requires[i].getVersionConstraint().getBundle()), requires[i]); + continue; + } + if (!developmentMode) { + // fail fast; otherwise we want to attempt to resolver other constraints in dev mode + failed = true; + break; + } + } + } + } + + if (!failed) { + // Iterate thru imports of 'bundle' trying to find matching exports. + 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], 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 + state.addResolverError(imports[i].getVersionConstraint().getBundle(), ResolverError.MISSING_IMPORT_PACKAGE, imports[i].getVersionConstraint().toString(), imports[i].getVersionConstraint()); + if (imports[i].isFromFragment()) { + if (!developmentMode) // only detach fragments when not in devmode + bundle.detachFragment(bundleMapping.get(imports[i].getVersionConstraint().getBundle()), imports[i]); + continue; + } + if (!developmentMode) { + // fail fast; otherwise we want to attempt to resolver other constraints in dev mode + failed = true; + break; + } + } + } + } + + // check that fragment constraints are met by the constraints that got resolved to the host + checkFragmentConstraints(bundle); + + // do some extra checking when in development mode to see if other resolver error occurred + if (developmentMode && !failed && state.getResolverErrors(bundle.getBundleDescription()).length > 0) + failed = true; + + // Need to check that all mandatory imports are wired. If they are then + // set the bundle RESOLVED, otherwise set it back to UNRESOLVED + if (failed) { + setBundleUnresolved(bundle, false, developmentMode); + if (DEBUG) + ResolverImpl.log(bundle + " NOT RESOLVED"); //$NON-NLS-1$ + } else if (!cycle.contains(bundle)) { + setBundleResolved(bundle); + if (DEBUG) + ResolverImpl.log(bundle + " RESOLVED"); //$NON-NLS-1$ + } + + if (bundle.getState() == ResolverBundle.UNRESOLVED) + bundle.setResolvable(false); // Set it to unresolvable so we don't attempt to resolve it again in this round + + return bundle.getState() != ResolverBundle.UNRESOLVED; + } + + private void checkFragmentConstraints(ResolverBundle bundle) { + // get all currently attached fragments and ensure that any constraints + // they have do not conflict with the constraints resolved to by the host + ResolverBundle[] fragments = bundle.getFragments(); + for (int i = 0; i < fragments.length; i++) { + BundleDescription fragment = fragments[i].getBundleDescription(); + if (bundle.constraintsConflict(fragment, fragment.getImportPackages(), fragment.getRequiredBundles(), fragment.getGenericRequires()) && !developmentMode) + // found some conflicts; detach the fragment + bundle.detachFragment(fragments[i], null); + } + } + + private boolean resolveGenericReq(GenericConstraint constraint, List<ResolverBundle> cycle) { + if (DEBUG_GENERICS) + ResolverImpl.log("Trying to resolve: " + constraint.getBundle() + ", " + constraint.getVersionConstraint()); //$NON-NLS-1$ //$NON-NLS-2$ + VersionSupplier matchingCapability = constraint.getSelectedSupplier(); + if (matchingCapability != null) { + if (!cycle.contains(constraint.getBundle())) { + cycle.add(constraint.getBundle()); + if (DEBUG_CYCLES) + ResolverImpl.log("generic cycle: " + constraint.getBundle() + " -> " + constraint.getSelectedSupplier()); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (DEBUG_GENERICS) + ResolverImpl.log(" - already wired"); //$NON-NLS-1$ + return true; // Already wired (due to grouping dependencies) so just return + } + List<GenericCapability> candidates; + long timestamp; + do { + timestamp = state.getTimeStamp(); + VersionHashMap<GenericCapability> namespace = resolverGenerics.get(constraint.getNameSpace()); + String name = constraint.getName(); + List<GenericCapability> capabilities; + if (namespace == null) + capabilities = Collections.EMPTY_LIST; + else + capabilities = name == null || name.indexOf('*') >= 0 ? namespace.getAllValues() : namespace.get(name); + candidates = new ArrayList<GenericCapability>(capabilities); + List<BundleCapability> genCapabilities = new ArrayList<BundleCapability>(candidates.size()); + // Must remove candidates that do not match before calling hooks. + for (Iterator<GenericCapability> iCandidates = candidates.iterator(); iCandidates.hasNext();) { + GenericCapability capability = iCandidates.next(); + if (!constraint.isSatisfiedBy(capability)) { + iCandidates.remove(); + } else { + genCapabilities.add(capability.getCapability()); + } + } + if (hook != null) + hook.filterMatches(constraint.getRequirement(), asCapabilities(new ArrayMap<BundleCapability, GenericCapability>(genCapabilities, candidates))); + } while (timestamp != state.getTimeStamp()); + boolean result = false; + // We are left with only capabilities that satisfy the constraint. + for (GenericCapability capability : candidates) { + if (DEBUG_GENERICS) + ResolverImpl.log("CHECKING GENERICS: " + capability.getBaseDescription()); //$NON-NLS-1$ + + // first add the possible supplier; this is done before resolving the supplier bundle to prevent endless cycle loops. + constraint.addPossibleSupplier(capability); // Wire to the capability + if (constraint.getBundle() == capability.getResolverBundle()) { + result = true; // Wired to ourselves + continue; + } + VersionSupplier[] capabilityHosts = capability.getResolverBundle().isFragment() ? capability.getResolverBundle().getHost().getPossibleSuppliers() : new ResolverBundle[] {capability.getResolverBundle()}; + boolean foundResolvedMatch = false; + for (int i = 0; capabilityHosts != null && i < capabilityHosts.length; i++) { + ResolverBundle capabilitySupplier = capabilityHosts[i].getResolverBundle(); + if (capabilitySupplier == constraint.getBundle()) { + // the capability is from a fragment attached to this host do not recursively resolve the host again + foundResolvedMatch = true; + continue; + } + boolean successfulResolve = false; + if (capabilitySupplier.getState() != ResolverBundle.RESOLVED) { + // only attempt to resolve the supplier if not osgi.ee name space + if (!StateImpl.OSGI_EE_NAMESPACE.equals(constraint.getNameSpace())) + successfulResolve = resolveBundle(capabilitySupplier, cycle); + } + + // if in dev mode then allow a constraint to resolve to an unresolved bundle + if (capabilitySupplier.getState() == ResolverBundle.RESOLVED || (successfulResolve || developmentMode)) { + foundResolvedMatch |= !capability.getResolverBundle().isFragment() ? true : capability.getResolverBundle().getHost().getPossibleSuppliers() != null; + // Check cyclic dependencies + if (capabilitySupplier.getState() == ResolverBundle.RESOLVING) + if (!cycle.contains(capabilitySupplier)) + cycle.add(capabilitySupplier); + } + } + if (!foundResolvedMatch) { + constraint.removePossibleSupplier(capability); + continue; // constraint hasn't resolved + } + if (DEBUG_GENERICS) + ResolverImpl.log("Found match: " + capability.getBaseDescription() + ". Wiring"); //$NON-NLS-1$ //$NON-NLS-2$ + result = true; + } + return result ? true : constraint.isOptional() || constraint.isFromRequiredEE(); + } + + // Resolve the supplied import. Returns true if the import can be resolved, false otherwise + private boolean resolveRequire(BundleConstraint req, List<ResolverBundle> cycle) { + if (DEBUG_REQUIRES) + ResolverImpl.log("Trying to resolve: " + req.getBundle() + ", " + req.getVersionConstraint()); //$NON-NLS-1$ //$NON-NLS-2$ + if (req.getSelectedSupplier() != null) { + // Check for unrecorded 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(" - already wired"); //$NON-NLS-1$ + return true; // Already wired (due to grouping dependencies) so just return + } + List<ResolverBundle> candidates; + long timestamp; + do { + timestamp = state.getTimeStamp(); + List<ResolverBundle> bundles = resolverBundles.get(req.getVersionConstraint().getName()); + candidates = new ArrayList<ResolverBundle>(bundles); + List<BundleCapability> capabilities = new ArrayList<BundleCapability>(candidates.size()); + // Must remove candidates that do not match before calling hooks. + for (Iterator<ResolverBundle> iCandidates = candidates.iterator(); iCandidates.hasNext();) { + ResolverBundle bundle = iCandidates.next(); + if (!req.isSatisfiedBy(bundle)) { + iCandidates.remove(); + } else { + capabilities.add(bundle.getCapability()); + } + } + if (hook != null) + hook.filterMatches(req.getRequirement(), asCapabilities(new ArrayMap<BundleCapability, ResolverBundle>(capabilities, candidates))); + } while (timestamp != state.getTimeStamp()); + // We are left with only capabilities that satisfy the require bundle. + boolean result = false; + for (ResolverBundle bundle : candidates) { + if (DEBUG_REQUIRES) + ResolverImpl.log("CHECKING: " + bundle.getBundleDescription()); //$NON-NLS-1$ + + // 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 (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.getBundleDescription() + ". Wiring"); //$NON-NLS-1$ //$NON-NLS-2$ + result = true; + } + + if (result || req.isOptional()) + return true; // If the req is optional then just return true + + return false; + } + + // Resolve the supplied import. Returns true if the import can be resolved, false otherwise + private boolean resolveImport(ResolverImport imp, List<ResolverBundle> cycle) { + if (DEBUG_IMPORTS) + ResolverImpl.log("Trying to resolve: " + imp.getBundle() + ", " + imp.getName()); //$NON-NLS-1$ //$NON-NLS-2$ + if (imp.getSelectedSupplier() != null) { + // Check for unrecorded 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().getBundleDescription()); //$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 + } + boolean result = false; + ResolverExport[] substitutableExps = imp.getBundle().getExports(imp.getName()); + long timestamp; + List<ResolverExport> candidates; + do { + timestamp = state.getTimeStamp(); + List<ResolverExport> exports = resolverExports.get(imp.getName()); + candidates = new ArrayList<ResolverExport>(exports); + List<BundleCapability> capabilities = new ArrayList<BundleCapability>(candidates.size()); + // Must remove candidates that do not match before calling hooks. + for (Iterator<ResolverExport> iCandidates = candidates.iterator(); iCandidates.hasNext();) { + ResolverExport export = iCandidates.next(); + if (!imp.isSatisfiedBy(export)) { + iCandidates.remove(); + } else { + capabilities.add(export.getCapability()); + } + } + if (hook != null) + hook.filterMatches(imp.getRequirement(), asCapabilities(new ArrayMap<BundleCapability, ResolverExport>(capabilities, candidates))); + } while (timestamp != state.getTimeStamp()); + // We are left with only capabilities that satisfy the import. + for (ResolverExport export : candidates) { + if (DEBUG_IMPORTS) + ResolverImpl.log("CHECKING: " + export.getExporter().getBundleDescription() + ", " + export.getName()); //$NON-NLS-1$ //$NON-NLS-2$ + + int originalState = export.getExporter().getState(); + if (imp.isDynamic() && originalState != ResolverBundle.RESOLVED) + continue; // Must not attempt to resolve an exporter when dynamic + if (imp.getSelectedSupplier() != null && ((ResolverExport) imp.getSelectedSupplier()).getExporter() == imp.getBundle()) + break; // We wired to ourselves; nobody else matters + // first add the possible supplier; this is done before resolving the supplier bundle to prevent endless cycle loops. + imp.addPossibleSupplier(export); + if (imp.getBundle() != export.getExporter()) { + for (int j = 0; j < substitutableExps.length; j++) + if (substitutableExps[j].getSubstitute() == null) + substitutableExps[j].setSubstitute(export); // Import wins, drop export + // if in dev mode then allow a constraint to resolve to an unresolved bundle + if ((originalState != ResolverBundle.RESOLVED && !resolveBundle(export.getExporter(), cycle) && !developmentMode) || export.getSubstitute() != null) { + // remove the possible supplier + imp.removePossibleSupplier(export); + // add back the exports of this package from the importer + if (imp.getSelectedSupplier() == null) + for (int j = 0; j < substitutableExps.length; j++) + if (substitutableExps[j].getSubstitute() == export) + substitutableExps[j].setSubstitute(null); + continue; // Bundle hasn't resolved || export has not been selected and is unavailable + } + } else if (export.getSubstitute() != null) + 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().getBundleDescription()); //$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; + } + + if (result) + return true; + if (imp.isOptional()) + return true; // If the import is optional then just return true + if (substitutableExps.length > 0 && substitutableExps[0].getSubstitute() == null) + return true; // If we still have an export that is not substituted return true + return false; + } + + // Move a bundle to UNRESOLVED + private void setBundleUnresolved(ResolverBundle bundle, boolean removed, boolean keepFragsAttached) { + if (bundle.getState() == ResolverBundle.UNRESOLVED && !developmentMode) + // in this case there is nothing more to do + return; + // Note that when in dev mode we only want to force the fragment detach if asked to; + // this would be done only when forcing a dependency chain to unresolve from unresolveBundle method + if (removed || !keepFragsAttached) { + // Force the initialization of the bundle, its exports and its capabilities. This is needed to force proper attachment of fragments. + resolverExports.remove(bundle.getExportPackages()); + removeGenerics(bundle.getGenericCapabilities()); + bundle.detachAllFragments(); + bundle.detachFromHosts(); + bundle.initialize(false); + if (!removed) { + // add back the available exports/capabilities + resolverExports.put(bundle.getExportPackages()); + addGenerics(bundle.getGenericCapabilities()); + } + } + // TODO unresolvedBundles should be a set; for now only need to do a contains check in devMode. + if (!removed && (!developmentMode || !unresolvedBundles.contains(bundle))) + unresolvedBundles.add(bundle); + bundle.setState(ResolverBundle.UNRESOLVED); + } + + // Move a bundle to RESOLVED + private void setBundleResolved(ResolverBundle bundle) { + if (bundle.getState() == ResolverBundle.RESOLVED) + return; + unresolvedBundles.remove(bundle); + bundle.setState(ResolverBundle.RESOLVED); + } + + // Move a bundle to RESOLVING + private void setBundleResolving(ResolverBundle bundle) { + if (bundle.getState() == ResolverBundle.RESOLVING) + return; + unresolvedBundles.remove(bundle); + bundle.setState(ResolverBundle.RESOLVING); + } + + // Resolves the bundles in the State + private void stateResolveBundles(ResolverBundle[] resolvedBundles) { + for (int i = 0; i < resolvedBundles.length; i++) { + if (!resolvedBundles[i].getBundleDescription().isResolved()) + stateResolveBundle(resolvedBundles[i]); + } + } + + private void stateResolveConstraints(ResolverBundle rb) { + ResolverImport[] imports = rb.getImportPackages(); + for (int i = 0; i < imports.length; i++) { + 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 = (ResolverBundle) requires[i].getSelectedSupplier(); + BaseDescription supplier = bundle == null ? null : bundle.getBundleDescription(); + state.resolveConstraint(requires[i].getVersionConstraint(), supplier); + } + GenericConstraint[] genericRequires = rb.getGenericRequires(); + for (int i = 0; i < genericRequires.length; i++) { + VersionSupplier[] matchingCapabilities = genericRequires[i].getMatchingCapabilities(); + if (matchingCapabilities == null) + state.resolveConstraint(genericRequires[i].getVersionConstraint(), null); + else + for (int j = 0; j < matchingCapabilities.length; j++) + state.resolveConstraint(genericRequires[i].getVersionConstraint(), matchingCapabilities[j].getBaseDescription()); + } + } + + private void stateResolveFragConstraints(ResolverBundle rb) { + ResolverBundle host = (ResolverBundle) rb.getHost().getSelectedSupplier(); + ImportPackageSpecification[] imports = rb.getBundleDescription().getImportPackages(); + for (int i = 0; i < imports.length; i++) { + ResolverImport hostImport = host == null ? null : host.getImport(imports[i].getName()); + ResolverExport export = (ResolverExport) (hostImport == null ? null : hostImport.getSelectedSupplier()); + BaseDescription supplier = export == null ? null : export.getExportPackageDescription(); + state.resolveConstraint(imports[i], supplier); + } + BundleSpecification[] requires = rb.getBundleDescription().getRequiredBundles(); + for (int i = 0; i < requires.length; i++) { + BundleConstraint hostRequire = host == null ? null : host.getRequire(requires[i].getName()); + ResolverBundle bundle = (ResolverBundle) (hostRequire == null ? null : hostRequire.getSelectedSupplier()); + BaseDescription supplier = bundle == null ? null : bundle.getBundleDescription(); + state.resolveConstraint(requires[i], supplier); + } + GenericConstraint[] genericRequires = rb.getGenericRequires(); + for (int i = 0; i < genericRequires.length; i++) { + VersionSupplier[] matchingCapabilities = genericRequires[i].getMatchingCapabilities(); + if (matchingCapabilities == null) + state.resolveConstraint(genericRequires[i].getVersionConstraint(), null); + else + for (int j = 0; j < matchingCapabilities.length; j++) + state.resolveConstraint(genericRequires[i].getVersionConstraint(), matchingCapabilities[j].getBaseDescription()); + } + } + + private void stateResolveBundle(ResolverBundle rb) { + // 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); + + // Build up the state wires + Map<String, List<StateWire>> stateWires = new HashMap<String, List<StateWire>>(); + + // Gather selected exports + ResolverExport[] exports = rb.getSelectedExports(); + List<ExportPackageDescription> selectedExports = new ArrayList<ExportPackageDescription>(exports.length); + for (int i = 0; i < exports.length; i++) { + if (permissionChecker.checkPackagePermission(exports[i].getExportPackageDescription())) + selectedExports.add(exports[i].getExportPackageDescription()); + } + ExportPackageDescription[] selectedExportsArray = selectedExports.toArray(new ExportPackageDescription[selectedExports.size()]); + + // Gather substitute exports + ResolverExport[] substituted = rb.getSubstitutedExports(); + List<ExportPackageDescription> substitutedExports = new ArrayList<ExportPackageDescription>(substituted.length); + for (int i = 0; i < substituted.length; i++) { + substitutedExports.add(substituted[i].getExportPackageDescription()); + } + ExportPackageDescription[] substitutedExportsArray = substitutedExports.toArray(new ExportPackageDescription[substitutedExports.size()]); + + // Gather exports that have been wired to + ExportPackageDescription[] exportsWiredToArray = getExportsWiredTo(rb, stateWires); + + // Gather bundles that have been wired to + BundleConstraint[] requires = rb.getRequires(); + List<BundleDescription> bundlesWiredTo = new ArrayList<BundleDescription>(requires.length); + List<StateWire> requireWires = new ArrayList<StateWire>(requires.length); + for (int i = 0; i < requires.length; i++) + if (requires[i].getSelectedSupplier() != null) { + BundleDescription supplier = (BundleDescription) requires[i].getSelectedSupplier().getBaseDescription(); + bundlesWiredTo.add(supplier); + StateWire requireWire = newStateWire(rb.getBundleDescription(), requires[i].getVersionConstraint(), supplier, supplier); + requireWires.add(requireWire); + } + BundleDescription[] bundlesWiredToArray = bundlesWiredTo.toArray(new BundleDescription[bundlesWiredTo.size()]); + if (!requireWires.isEmpty()) + stateWires.put(BundleRevision.BUNDLE_NAMESPACE, requireWires); + + GenericCapability[] capabilities = rb.getGenericCapabilities(); + List<GenericDescription> selectedCapabilities = new ArrayList<GenericDescription>(capabilities.length); + for (GenericCapability capability : capabilities) + if (capability.isEffective() && permissionChecker.checkCapabilityPermission(capability.getGenericDescription())) + selectedCapabilities.add(capability.getGenericDescription()); + GenericDescription[] selectedCapabilitiesArray = selectedCapabilities.toArray(new GenericDescription[selectedCapabilities.size()]); + + GenericConstraint[] genericRequires = rb.getGenericRequires(); + List<GenericDescription> resolvedGenericRequires = new ArrayList<GenericDescription>(genericRequires.length); + for (GenericConstraint genericConstraint : genericRequires) { + VersionSupplier[] matching = genericConstraint.getMatchingCapabilities(); + if (matching != null) + for (VersionSupplier capability : matching) { + GenericDescription supplier = ((GenericCapability) capability).getGenericDescription(); + resolvedGenericRequires.add(supplier); + StateWire genericWire = newStateWire(rb.getBundleDescription(), genericConstraint.getVersionConstraint(), supplier.getSupplier(), supplier); + List<StateWire> genericWires = stateWires.get(genericConstraint.getNameSpace()); + if (genericWires == null) { + genericWires = new ArrayList<StateWire>(); + stateWires.put(genericConstraint.getNameSpace(), genericWires); + } + genericWires.add(genericWire); + } + } + GenericDescription[] capabilitiesWiredToArray = resolvedGenericRequires.toArray(new GenericDescription[resolvedGenericRequires.size()]); + + BundleDescription[] hostBundles = null; + if (rb.isFragment()) { + VersionSupplier[] matchingBundles = rb.getHost().getPossibleSuppliers(); + if (matchingBundles != null && matchingBundles.length > 0) { + hostBundles = new BundleDescription[matchingBundles.length]; + List<StateWire> hostWires = new ArrayList<StateWire>(matchingBundles.length); + stateWires.put(BundleRevision.HOST_NAMESPACE, hostWires); + for (int i = 0; i < matchingBundles.length; i++) { + hostBundles[i] = matchingBundles[i].getBundleDescription(); + StateWire hostWire = newStateWire(rb.getBundleDescription(), rb.getHost().getVersionConstraint(), hostBundles[i], hostBundles[i]); + hostWires.add(hostWire); + if (hostBundles[i].isResolved()) { + ExportPackageDescription[] newSelectedExports = null; + GenericDescription[] newSelectedCapabilities = null; + if (rb.isNewFragmentExports()) { + // update the host's set of selected exports + ResolverExport[] hostExports = ((ResolverBundle) matchingBundles[i]).getSelectedExports(); + newSelectedExports = new ExportPackageDescription[hostExports.length]; + for (int j = 0; j < hostExports.length; j++) + newSelectedExports[j] = hostExports[j].getExportPackageDescription(); + } + if (rb.isNewFragmentCapabilities()) { + // update the host's set of selected capabilities + GenericCapability[] hostCapabilities = ((ResolverBundle) matchingBundles[i]).getGenericCapabilities(); + newSelectedCapabilities = new GenericDescription[hostCapabilities.length]; + for (int j = 0; j < hostCapabilities.length; j++) + newSelectedCapabilities[j] = hostCapabilities[j].getGenericDescription(); + } + if (newSelectedCapabilities != null || newSelectedExports != null) { + if (newSelectedCapabilities == null) + newSelectedCapabilities = hostBundles[i].getSelectedGenericCapabilities(); + if (newSelectedExports == null) + newSelectedExports = hostBundles[i].getSelectedExports(); + state.resolveBundle(hostBundles[i], true, null, newSelectedExports, hostBundles[i].getSubstitutedExports(), newSelectedCapabilities, hostBundles[i].getResolvedRequires(), hostBundles[i].getResolvedImports(), hostBundles[i].getResolvedGenericRequires(), ((BundleDescriptionImpl) hostBundles[i]).getWires()); + } + } + } + } + } + + // Resolve the bundle in the state + state.resolveBundle(rb.getBundleDescription(), rb.isResolved(), hostBundles, selectedExportsArray, substitutedExportsArray, selectedCapabilitiesArray, bundlesWiredToArray, exportsWiredToArray, capabilitiesWiredToArray, stateWires); + } + + private static ExportPackageDescription[] getExportsWiredTo(ResolverBundle rb, Map<String, List<StateWire>> stateWires) { + // Gather exports that have been wired to + ResolverImport[] imports = rb.getImportPackages(); + List<ExportPackageDescription> exportsWiredTo = new ArrayList<ExportPackageDescription>(imports.length); + List<StateWire> importWires = new ArrayList<StateWire>(imports.length); + for (int i = 0; i < imports.length; i++) + if (imports[i].getSelectedSupplier() != null) { + ExportPackageDescription supplier = (ExportPackageDescription) imports[i].getSelectedSupplier().getBaseDescription(); + exportsWiredTo.add(supplier); + StateWire wire = newStateWire(rb.getBundleDescription(), imports[i].getVersionConstraint(), supplier.getExporter(), supplier); + importWires.add(wire); + } + if (stateWires != null && !importWires.isEmpty()) + stateWires.put(BundleRevision.PACKAGE_NAMESPACE, importWires); + return exportsWiredTo.toArray(new ExportPackageDescription[exportsWiredTo.size()]); + } + + private static StateWire newStateWire(BundleDescription requirementHost, VersionConstraint declaredRequirement, BundleDescription capabilityHost, BaseDescription declaredCapability) { + BaseDescription fragDeclared = ((BaseDescriptionImpl) declaredCapability).getFragmentDeclaration(); + declaredCapability = fragDeclared != null ? fragDeclared : declaredCapability; + return new StateWire(requirementHost, declaredRequirement, capabilityHost, declaredCapability); + } + + // Resolve dynamic import + public synchronized ExportPackageDescription resolveDynamicImport(BundleDescription importingBundle, String requestedPackage) { + if (state == null) + throw new IllegalStateException("RESOLVER_NO_STATE"); //$NON-NLS-1$ + + // Make sure the resolver is initialized + if (!initialized) + initialize(); + hook = (state instanceof StateImpl) ? ((StateImpl) state).getResolverHook() : null; + try { + ResolverBundle rb = bundleMapping.get(importingBundle); + if (rb.getExport(requestedPackage) != null) + return null; // do not allow dynamic wires for packages which this bundle exports + ResolverImport[] resolverImports = rb.getImportPackages(); + // Check through the ResolverImports of this bundle. + // If there is a matching one then pass it into resolveImport() + for (int j = 0; j < resolverImports.length; j++) { + // Make sure it is a dynamic import + if (!resolverImports[j].isDynamic()) + continue; + // Resolve the import + ExportPackageDescription supplier = resolveDynamicImport(resolverImports[j], requestedPackage); + if (supplier != null) + return supplier; + } + // look for packages added dynamically + ImportPackageSpecification[] addedDynamicImports = importingBundle.getAddedDynamicImportPackages(); + for (ImportPackageSpecification addedDynamicImport : addedDynamicImports) { + ResolverImport newImport = new ResolverImport(rb, addedDynamicImport); + ExportPackageDescription supplier = resolveDynamicImport(newImport, requestedPackage); + if (supplier != null) + return supplier; + } + + if (DEBUG || DEBUG_IMPORTS) + ResolverImpl.log("Failed to resolve dynamic import: " + requestedPackage); //$NON-NLS-1$ + return null; // Couldn't resolve the import, so return null + } finally { + hook = null; + } + } + + private void addStateWire(BundleDescription importingBundle, VersionConstraint requirement, BundleDescription capabilityHost, ExportPackageDescription capability) { + Map<String, List<StateWire>> wires = ((BundleDescriptionImpl) importingBundle).getWires(); + List<StateWire> imports = wires.get(BundleRevision.PACKAGE_NAMESPACE); + if (imports == null) { + imports = new ArrayList<StateWire>(); + wires.put(BundleRevision.PACKAGE_NAMESPACE, imports); + } + imports.add(newStateWire(importingBundle, requirement, capabilityHost, capability)); + } + + private ExportPackageDescription resolveDynamicImport(ResolverImport dynamicImport, String requestedPackage) { + String importName = dynamicImport.getName(); + // If the import uses a wildcard, then temporarily replace this with the requested package + if (importName.equals("*") || //$NON-NLS-1$ + (importName.endsWith(".*") && requestedPackage.startsWith(importName.substring(0, importName.length() - 1)))) { //$NON-NLS-1$ + dynamicImport.setName(requestedPackage); + } + try { + // Resolve the import + if (!requestedPackage.equals(dynamicImport.getName())) + return null; + + if (resolveImport(dynamicImport, new ArrayList<ResolverBundle>())) { + // populate the grouping checker with current imports + groupingChecker.populateRoots(dynamicImport.getBundle()); + while (dynamicImport.getSelectedSupplier() != null) { + if (groupingChecker.isDynamicConsistent(dynamicImport.getBundle(), (ResolverExport) dynamicImport.getSelectedSupplier()) != null) { + dynamicImport.selectNextSupplier(); // not consistent; try the next + } else { + // If the import resolved then return it's matching export + if (DEBUG_IMPORTS) + ResolverImpl.log("Resolved dynamic import: " + dynamicImport.getBundle() + ":" + dynamicImport.getName() + " -> " + ((ResolverExport) dynamicImport.getSelectedSupplier()).getExporter() + ":" + requestedPackage); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + + // now that we have an export to wire to; populate the roots for that package for the bundle + ResolverExport export = (ResolverExport) dynamicImport.getSelectedSupplier(); + groupingChecker.populateRoots(dynamicImport.getBundle(), export); + + ExportPackageDescription supplier = export.getExportPackageDescription(); + if (supplier != null) + addStateWire(dynamicImport.getBundleDescription(), dynamicImport.getVersionConstraint(), supplier.getExporter(), supplier); + return supplier; + } + } + dynamicImport.clearPossibleSuppliers(); + } + } finally { + // 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$ + dynamicImport.clearPossibleSuppliers(); + // Reset the import package name + dynamicImport.setName(null); + } + return null; + } + + public void bundleAdded(BundleDescription bundle) { + if (!initialized) + return; + + if (bundleMapping.get(bundle) != null) + return; // this description already exists in the resolver + ResolverBundle rb = new ResolverBundle(bundle, this); + bundleMapping.put(bundle, rb); + unresolvedBundles.add(rb); + resolverExports.put(rb.getExportPackages()); + resolverBundles.put(rb.getName(), rb); + addGenerics(rb.getGenericCapabilities()); + if (hook != null && rb.isFragment()) { + attachFragment0(rb); + } + } + + public void bundleRemoved(BundleDescription bundle, boolean pending) { + ResolverBundle rb = initialized ? (ResolverBundle) bundleMapping.get(bundle) : null; + if (rb != null) + rb.setUninstalled(); + internalBundleRemoved(bundle, pending); + } + + private void internalBundleRemoved(BundleDescription bundle, boolean pending) { + // check if there are any dependants + if (pending) + removalPending.put(new Long(bundle.getBundleId()), bundle); + if (!initialized) + return; + ResolverBundle rb = bundleMapping.get(bundle); + if (rb == null) + return; + + if (!pending) { + bundleMapping.remove(bundle); + groupingChecker.clear(rb); + } + if (!pending || !bundle.isResolved()) { + resolverExports.remove(rb.getExportPackages()); + resolverBundles.remove(rb); + removeGenerics(rb.getGenericCapabilities()); + } + unresolvedBundles.remove(rb); + } + + private void unresolveBundle(ResolverBundle bundle, boolean removed) { + if (bundle == null) + return; + // check the removed list if unresolving then remove from the removed list + List<BundleDescription> removedBundles = removalPending.remove(new Long(bundle.getBundleDescription().getBundleId())); + for (BundleDescription removedDesc : removedBundles) { + ResolverBundle re = bundleMapping.get(removedDesc); + unresolveBundle(re, true); + state.removeBundleComplete(removedDesc); + resolverExports.remove(re.getExportPackages()); + resolverBundles.remove(re); + removeGenerics(re.getGenericCapabilities()); + bundleMapping.remove(removedDesc); + groupingChecker.clear(re); + // the bundle is removed + if (removedDesc == bundle.getBundleDescription()) + removed = true; + } + + if (!bundle.getBundleDescription().isResolved() && !developmentMode) + return; + CompositeResolveHelperRegistry currentLinks = compositeHelpers; + if (currentLinks != null) { + CompositeResolveHelper helper = currentLinks.getCompositeResolveHelper(bundle.getBundleDescription()); + if (helper != null) + helper.giveExports(null); + } + // if not removed then add to the list of unresolvedBundles, + // passing false for devmode because we need all fragments detached + setBundleUnresolved(bundle, removed, false); + // Get bundles dependent on 'bundle' + BundleDescription[] dependents = bundle.getBundleDescription().getDependents(); + state.resolveBundle(bundle.getBundleDescription(), false, null, null, null, null, null, null, null, null); + // Unresolve dependents of 'bundle' + for (int i = 0; i < dependents.length; i++) + unresolveBundle(bundleMapping.get(dependents[i]), false); + } + + public void bundleUpdated(BundleDescription newDescription, BundleDescription existingDescription, boolean pending) { + internalBundleRemoved(existingDescription, pending); + bundleAdded(newDescription); + } + + public void flush() { + resolverExports = null; + resolverBundles = null; + resolverGenerics = null; + unresolvedBundles = null; + bundleMapping = null; + List<BundleDescription> removed = removalPending.getAllValues(); + for (BundleDescription removedDesc : removed) + state.removeBundleComplete(removedDesc); + removalPending.clear(); + initialized = false; + } + + public State getState() { + return state; + } + + public void setState(State newState) { + if (this.state != null) { + throw new IllegalStateException("Cannot change the State of a Resolver"); //$NON-NLS-1$ + } + state = newState; + flush(); + } + + private void setDebugOptions() { + FrameworkDebugOptions options = null; //FrameworkDebugOptions.getDefault(); + // may be null if debugging is not enabled + if (options == null) + return; + DEBUG = options.getBooleanOption(OPTION_DEBUG, false); + DEBUG_WIRING = options.getBooleanOption(OPTION_WIRING, false); + DEBUG_IMPORTS = options.getBooleanOption(OPTION_IMPORTS, false); + DEBUG_REQUIRES = options.getBooleanOption(OPTION_REQUIRES, false); + DEBUG_GENERICS = options.getBooleanOption(OPTION_GENERICS, false); + DEBUG_USES = options.getBooleanOption(OPTION_USES, false); + DEBUG_CYCLES = options.getBooleanOption(OPTION_CYCLES, false); + } + + // LOGGING METHODS + private void printWirings() { + ResolverImpl.log("****** Result Wirings ******"); //$NON-NLS-1$ + List<ResolverBundle> bundles = resolverBundles.getAllValues(); + for (ResolverBundle rb : bundles) { + if (rb.getBundleDescription().isResolved()) { + continue; + } + ResolverImpl.log(" * WIRING for " + rb); //$NON-NLS-1$ + // Require bundles + BundleConstraint[] requireBundles = rb.getRequires(); + if (requireBundles.length == 0) { + ResolverImpl.log(" (r) no requires"); //$NON-NLS-1$ + } else { + for (int i = 0; i < requireBundles.length; i++) { + if (requireBundles[i].getSelectedSupplier() == null) { + ResolverImpl.log(" (r) " + rb.getBundleDescription() + " -> NULL!!!"); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + ResolverImpl.log(" (r) " + rb.getBundleDescription() + " -> " + requireBundles[i].getSelectedSupplier()); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + } + // Hosts + BundleConstraint hostSpec = rb.getHost(); + if (hostSpec != null) { + VersionSupplier[] hosts = hostSpec.getPossibleSuppliers(); + if (hosts != null) + for (int i = 0; i < hosts.length; i++) { + ResolverImpl.log(" (h) " + rb.getBundleDescription() + " -> " + hosts[i].getBundleDescription()); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + // Imports + ResolverImport[] imports = rb.getImportPackages(); + if (imports.length == 0) { + ResolverImpl.log(" (w) no imports"); //$NON-NLS-1$ + continue; + } + for (int i = 0; i < imports.length; i++) { + 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].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].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$ + ((ResolverExport) imports[i].getSelectedSupplier()).getExporter() + ":" + imports[i].getSelectedSupplier().getName()); //$NON-NLS-1$ + } + } + } + } + + static void log(String message) { + Debug.println(message); + } + + VersionHashMap<ResolverExport> getResolverExports() { + return resolverExports; + } + + public void setSelectionPolicy(Comparator<BaseDescription> selectionPolicy) { + this.selectionPolicy = selectionPolicy; + } + + public Comparator<BaseDescription> getSelectionPolicy() { + return selectionPolicy; + } + + public void setCompositeResolveHelperRegistry(CompositeResolveHelperRegistry compositeHelpers) { + this.compositeHelpers = compositeHelpers; + } + + CompositeResolveHelperRegistry getCompositeHelpers() { + return compositeHelpers; + } + + private void reorderGenerics() { + for (VersionHashMap<GenericCapability> namespace : resolverGenerics.values()) + namespace.reorder(); + } + + void removeGenerics(GenericCapability[] generics) { + for (GenericCapability capability : generics) { + VersionHashMap<GenericCapability> namespace = resolverGenerics.get(capability.getGenericDescription().getType()); + if (namespace != null) + namespace.remove(capability); + } + } + + void addGenerics(GenericCapability[] generics) { + for (GenericCapability capability : generics) { + if (!capability.isEffective()) + continue; + String type = capability.getGenericDescription().getType(); + VersionHashMap<GenericCapability> namespace = resolverGenerics.get(type); + if (namespace == null) { + namespace = new VersionHashMap<GenericCapability>(this); + resolverGenerics.put(type, namespace); + } + namespace.put(capability.getName(), capability); + } + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverImport.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverImport.java new file mode 100644 index 000000000..848841ba6 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/ResolverImport.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * 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 org.eclipse.osgi.service.resolver.ImportPackageSpecification; +import org.osgi.framework.Constants; + +/* + * A companion to ImportPackageSpecification from the state used while resolving + */ +public class ResolverImport extends ResolverConstraint { + // only used for dynamic imports + private String name; + + ResolverImport(ResolverBundle bundle, ImportPackageSpecification ips) { + super(bundle, ips); + } + + boolean isOptional() { + return ImportPackageSpecification.RESOLUTION_OPTIONAL.equals(((ImportPackageSpecification) constraint).getDirective(Constants.RESOLUTION_DIRECTIVE)); + } + + boolean isDynamic() { + return ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(((ImportPackageSpecification) constraint).getDirective(Constants.RESOLUTION_DIRECTIVE)); + } + + public String getName() { + if (name != null) + return name; // return the required package set for a dynamic import + return super.getName(); + } + + // used for dynamic import package when wildcards are used + void setName(String requestedPackage) { + this.name = requestedPackage; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/VersionHashMap.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/VersionHashMap.java new file mode 100644 index 000000000..b27b03876 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/VersionHashMap.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2004, 2011 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 + * Danail Nachev - ProSyst - bug 218625 + *******************************************************************************/ +package org.eclipse.osgi.internal.module; + +import java.util.*; + +public class VersionHashMap<V extends VersionSupplier> extends MappedList<String, V> implements Comparator<V> { + private final ResolverImpl resolver; + private final boolean preferSystemPackages; + + public VersionHashMap(ResolverImpl resolver) { + this.resolver = resolver; + Dictionary<?, ?>[] allProperties = resolver.getState().getPlatformProperties(); + Object preferSystem = allProperties.length == 0 ? "true" : allProperties[0].get("osgi.resolver.preferSystemPackages"); //$NON-NLS-1$//$NON-NLS-2$ + if (preferSystem == null) + preferSystem = "true"; //$NON-NLS-1$ + preferSystemPackages = Boolean.valueOf(preferSystem.toString()).booleanValue(); + } + + // assumes existing array is sorted + // finds the index where to insert the new value + protected int insertionIndex(List<V> existing, V value) { + int index = existing.size(); + if (compare(existing.get(existing.size() - 1), value) > 0) { + index = Collections.binarySearch(existing, value, this); + + if (index < 0) + index = -index - 1; + } + return index; + } + + public void put(V[] versionSuppliers) { + for (int i = 0; i < versionSuppliers.length; i++) + put(versionSuppliers[i].getName(), versionSuppliers[i]); + } + + public boolean contains(V vs) { + return contains(vs, false) != null; + } + + private V contains(V vs, boolean remove) { + List<V> existing = internal.get(vs.getName()); + if (existing == null) + return null; + int index = existing.indexOf(vs); + if (index >= 0) { + if (remove) { + existing.remove(index); + if (existing.size() == 0) + internal.remove(vs.getName()); + } + return vs; + } + return null; + } + + public V remove(V toBeRemoved) { + return contains(toBeRemoved, true); + } + + public void remove(V[] versionSuppliers) { + for (int i = 0; i < versionSuppliers.length; i++) + remove(versionSuppliers[i]); + } + + // Once we have resolved bundles, we need to make sure that version suppliers + // from the resolved bundles are ahead of those from unresolved bundles + void reorder() { + for (Iterator<List<V>> it = internal.values().iterator(); it.hasNext();) { + List<V> existing = it.next(); + if (existing.size() > 1) + Collections.sort(existing, this); + } + } + + // Compares two VersionSuppliers for descending ordered sorts. + // The VersionSuppliers are sorted by the following priorities + // First the resolution status of the supplying bundle. + // Second is the supplier version. + // Third is the bundle id of the supplying bundle. + public int compare(V vs1, V vs2) { + // if the selection policy is set then use that + if (resolver.getSelectionPolicy() != null) + return resolver.getSelectionPolicy().compare(vs1.getBaseDescription(), vs2.getBaseDescription()); + if (preferSystemPackages) { + String systemBundle = resolver.getSystemBundle(); + if (systemBundle.equals(vs1.getBundleDescription().getSymbolicName()) && !systemBundle.equals(vs2.getBundleDescription().getSymbolicName())) + return -1; + else if (!systemBundle.equals(vs1.getBundleDescription().getSymbolicName()) && systemBundle.equals(vs2.getBundleDescription().getSymbolicName())) + return 1; + } + if (vs1.getBundleDescription().isResolved() != vs2.getBundleDescription().isResolved()) + return vs1.getBundleDescription().isResolved() ? -1 : 1; + int versionCompare = -(vs1.getVersion().compareTo(vs2.getVersion())); + if (versionCompare != 0) + return versionCompare; + return vs1.getBundleDescription().getBundleId() <= vs2.getBundleDescription().getBundleId() ? -1 : 1; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/VersionSupplier.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/VersionSupplier.java new file mode 100644 index 000000000..03c94b021 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/module/VersionSupplier.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2004, 2011 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 org.eclipse.osgi.service.resolver.BaseDescription; +import org.eclipse.osgi.service.resolver.BundleDescription; +import org.osgi.framework.Version; +import org.osgi.framework.wiring.BundleCapability; + +/* + * A companion to BaseDescription from the state used while resolving. + */ +public abstract class VersionSupplier { + final protected BaseDescription base; + final private BundleCapability capability; + private VersionSupplier substitute; + + VersionSupplier(BaseDescription base) { + this.base = base; + this.capability = base.getCapability(); + } + + public Version getVersion() { + return base.getVersion(); + } + + public String getName() { + return base.getName(); + } + + public BaseDescription getBaseDescription() { + return base; + } + + // returns the version supplier that has been substituted for this version supplier + VersionSupplier getSubstitute() { + return substitute; + } + + // sets the dropped status. This should only be called by the VersionHashMap + // when VersionSuppliers are removed + void setSubstitute(VersionSupplier substitute) { + this.substitute = substitute; + } + + /* + * returns the BundleDescription which supplies this VersionSupplier + */ + abstract public BundleDescription getBundleDescription(); + + /* + * returns the ResolverBundle which supplies this VersionSupplier + */ + abstract ResolverBundle getResolverBundle(); + + public String toString() { + return base.toString(); + } + + BundleCapability getCapability() { + return capability; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/BaseDescriptionImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/BaseDescriptionImpl.java new file mode 100644 index 000000000..60d3f2b30 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/BaseDescriptionImpl.java @@ -0,0 +1,174 @@ +/******************************************************************************* + * Copyright (c) 2004, 2011 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import java.util.*; +import java.util.Map.Entry; +import org.eclipse.osgi.service.resolver.BaseDescription; +import org.osgi.framework.Version; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleRevision; + +public abstract class BaseDescriptionImpl implements BaseDescription { + + protected final Object monitor = new Object(); + + private volatile String name; + + private volatile Version version; + + private volatile Object userObject; + + public String getName() { + return name; + } + + public Version getVersion() { + synchronized (this.monitor) { + if (version == null) + return Version.emptyVersion; + return version; + } + } + + protected void setName(String name) { + this.name = name; + } + + protected void setVersion(Version version) { + this.version = version; + } + + static <V> String toString(Map<String, V> map, boolean directives) { + if (map.size() == 0) + return ""; //$NON-NLS-1$ + String assignment = directives ? ":=" : "="; //$NON-NLS-1$//$NON-NLS-2$ + Set<Entry<String, V>> set = map.entrySet(); + StringBuffer sb = new StringBuffer(); + for (Entry<String, V> entry : set) { + sb.append("; "); //$NON-NLS-1$ + String key = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof List) { + @SuppressWarnings("unchecked") + List<Object> list = (List<Object>) value; + if (list.size() == 0) + continue; + Object component = list.get(0); + String className = component.getClass().getName(); + String type = className.substring(className.lastIndexOf('.') + 1); + sb.append(key).append(':').append("List<").append(type).append(">").append(assignment).append('"'); //$NON-NLS-1$ //$NON-NLS-2$ + for (Object object : list) + sb.append(object).append(','); + sb.setLength(sb.length() - 1); + sb.append('"'); + } else { + String type = ""; //$NON-NLS-1$ + if (!(value instanceof String)) { + String className = value.getClass().getName(); + type = ":" + className.substring(className.lastIndexOf('.') + 1); //$NON-NLS-1$ + } + sb.append(key).append(type).append(assignment).append('"').append(value).append('"'); + } + } + return sb.toString(); + } + + String getInternalNameSpace() { + return null; + } + + public BaseDescription getFragmentDeclaration() { + return null; + } + + public BundleCapability getCapability() { + return getCapability(null); + } + + BundleCapability getCapability(String namespace) { + BaseDescriptionImpl fragmentDeclaration = (BaseDescriptionImpl) getFragmentDeclaration(); + if (fragmentDeclaration != null) + return fragmentDeclaration.getCapability(namespace); + if (namespace == null) + namespace = getInternalNameSpace(); + if (namespace == null) + return null; + return new BaseCapability(namespace); + } + + public Object getUserObject() { + return userObject; + } + + public void setUserObject(Object userObject) { + this.userObject = userObject; + } + + class BaseCapability implements BundleCapability { + private final String namespace; + + public BaseCapability(String namespace) { + super(); + this.namespace = namespace; + } + + public BundleRevision getRevision() { + return getSupplier(); + } + + public String getNamespace() { + return namespace; + } + + public Map<String, String> getDirectives() { + return getDeclaredDirectives(); + } + + public Map<String, Object> getAttributes() { + Map<String, Object> attrs = getDeclaredAttributes(); + String internalName = BaseDescriptionImpl.this.getInternalNameSpace(); + if (namespace.equals(internalName)) + return attrs; + // we are doing an alias, must remove internal Name and add alias + attrs = new HashMap<String, Object>(attrs); + Object nameValue = attrs.remove(internalName); + if (nameValue != null) + attrs.put(namespace, nameValue); + return Collections.unmodifiableMap(attrs); + } + + public int hashCode() { + return System.identityHashCode(BaseDescriptionImpl.this); + } + + protected BaseDescriptionImpl getBaseDescription() { + return BaseDescriptionImpl.this; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof BaseCapability)) + return false; + return (((BaseCapability) obj).getBaseDescription() == BaseDescriptionImpl.this) && namespace.equals(((BaseCapability) obj).getNamespace()); + } + + public String toString() { + return getNamespace() + BaseDescriptionImpl.toString(getAttributes(), false) + BaseDescriptionImpl.toString(getDirectives(), true); + } + + public BundleRevision getResource() { + return getRevision(); + } + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/BundleDeltaImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/BundleDeltaImpl.java new file mode 100644 index 000000000..448e37dbb --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/BundleDeltaImpl.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2003, 2010 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import org.eclipse.osgi.service.resolver.BundleDelta; +import org.eclipse.osgi.service.resolver.BundleDescription; + +final class BundleDeltaImpl implements BundleDelta { + + private volatile BundleDescription bundleDescription; + private volatile int type; + + public BundleDeltaImpl(BundleDescription bundleDescription) { + this(bundleDescription, 0); + } + + public BundleDeltaImpl(BundleDescription bundleDescription, int type) { + this.bundleDescription = bundleDescription; + this.type = type; + } + + public BundleDescription getBundle() { + return bundleDescription; + } + + public int getType() { + return type; + } + + protected void setBundle(BundleDescription bundleDescription) { + this.bundleDescription = bundleDescription; + } + + protected void setType(int type) { + this.type = type; + } + + public String toString() { + return bundleDescription.getSymbolicName() + '_' + bundleDescription.getVersion() + " (" + toTypeString(type) + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } + + private static String toTypeString(int type) { + StringBuffer typeStr = new StringBuffer(); + if ((type & BundleDelta.ADDED) != 0) + typeStr.append("ADDED,"); //$NON-NLS-1$ + if ((type & BundleDelta.REMOVED) != 0) + typeStr.append("REMOVED,"); //$NON-NLS-1$ + if ((type & BundleDelta.RESOLVED) != 0) + typeStr.append("RESOLVED,"); //$NON-NLS-1$ + if ((type & BundleDelta.UNRESOLVED) != 0) + typeStr.append("UNRESOLVED,"); //$NON-NLS-1$ + if ((type & BundleDelta.LINKAGE_CHANGED) != 0) + typeStr.append("LINKAGE_CHANGED,"); //$NON-NLS-1$ + if ((type & BundleDelta.UPDATED) != 0) + typeStr.append("UPDATED,"); //$NON-NLS-1$ + if ((type & BundleDelta.REMOVAL_PENDING) != 0) + typeStr.append("REMOVAL_PENDING,"); //$NON-NLS-1$ + if ((type & BundleDelta.REMOVAL_COMPLETE) != 0) + typeStr.append("REMOVAL_COMPLETE,"); //$NON-NLS-1$ + if (typeStr.length() > 0) + typeStr.deleteCharAt(typeStr.length() - 1); + return typeStr.toString(); + } + + public int compareTo(BundleDelta obj) { + long idcomp = getBundle().getBundleId() - obj.getBundle().getBundleId(); + return (idcomp < 0L) ? -1 : ((idcomp > 0L) ? 1 : 0); + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/BundleDescriptionImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/BundleDescriptionImpl.java new file mode 100644 index 000000000..ee6cdb91c --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/BundleDescriptionImpl.java @@ -0,0 +1,1244 @@ +/******************************************************************************* + * Copyright (c) 2003, 2012 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 + * Danail Nachev - ProSyst - bug 218625 + * Rob Harrop - SpringSource Inc. (bug 247522 and 255520) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import java.io.IOException; +import java.net.URL; +import java.util.*; +import org.eclipse.osgi.framework.util.KeyedElement; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.*; +import org.osgi.framework.wiring.*; +import org.osgi.resource.*; + +public final class BundleDescriptionImpl extends BaseDescriptionImpl implements BundleDescription, KeyedElement { + static final String[] EMPTY_STRING = new String[0]; + static final ImportPackageSpecification[] EMPTY_IMPORTS = new ImportPackageSpecification[0]; + static final BundleSpecification[] EMPTY_BUNDLESPECS = new BundleSpecification[0]; + static final ExportPackageDescription[] EMPTY_EXPORTS = new ExportPackageDescription[0]; + static final BundleDescription[] EMPTY_BUNDLEDESCS = new BundleDescription[0]; + static final GenericSpecification[] EMPTY_GENERICSPECS = new GenericSpecification[0]; + static final GenericDescription[] EMPTY_GENERICDESCS = new GenericDescription[0]; + static final RuntimePermission GET_CLASSLOADER_PERM = new RuntimePermission("getClassLoader"); //$NON-NLS-1$ + + static final int RESOLVED = 0x01; + static final int SINGLETON = 0x02; + static final int REMOVAL_PENDING = 0x04; + static final int FULLY_LOADED = 0x08; + static final int LAZY_LOADED = 0x10; + static final int HAS_DYNAMICIMPORT = 0x20; + static final int ATTACH_FRAGMENTS = 0x40; + static final int DYNAMIC_FRAGMENTS = 0x80; + + // set to fully loaded and allow dynamic fragments by default + private volatile int stateBits = FULLY_LOADED | ATTACH_FRAGMENTS | DYNAMIC_FRAGMENTS; + + private volatile long bundleId = -1; + volatile HostSpecification host; //null if the bundle is not a fragment. volatile to allow unsynchronized checks for null + private volatile StateImpl containingState; + + private volatile int lazyDataOffset = -1; + private volatile int lazyDataSize = -1; + + private List<BundleDescription> dependencies; + private List<BundleDescription> dependents; + private String[] mandatory; + private Map<String, Object> attributes; + private Map<String, String> arbitraryDirectives; + + private volatile LazyData lazyData; + private volatile int equinox_ee = -1; + + private DescriptionWiring bundleWiring; + + public BundleDescriptionImpl() { + // + } + + public long getBundleId() { + return bundleId; + } + + public String getSymbolicName() { + return getName(); + } + + public BundleDescription getSupplier() { + return this; + } + + public String getLocation() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + return currentData.location; + } + } + + public String getPlatformFilter() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + return currentData.platformFilter; + } + } + + public String[] getExecutionEnvironments() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.executionEnvironments == null) + return EMPTY_STRING; + return currentData.executionEnvironments; + } + } + + public ImportPackageSpecification[] getImportPackages() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.importPackages == null) + return EMPTY_IMPORTS; + return currentData.importPackages; + } + } + + public ImportPackageSpecification[] getAddedDynamicImportPackages() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.addedDynamicImports == null) + return EMPTY_IMPORTS; + return currentData.addedDynamicImports.toArray(new ImportPackageSpecification[currentData.addedDynamicImports.size()]); + } + } + + public BundleSpecification[] getRequiredBundles() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.requiredBundles == null) + return EMPTY_BUNDLESPECS; + return currentData.requiredBundles; + } + } + + public GenericSpecification[] getGenericRequires() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.genericRequires == null) + return EMPTY_GENERICSPECS; + return currentData.genericRequires; + } + } + + public GenericDescription[] getGenericCapabilities() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.genericCapabilities == null) + return EMPTY_GENERICDESCS; + return currentData.genericCapabilities; + } + } + + public NativeCodeSpecification getNativeCodeSpecification() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + return currentData.nativeCode; + } + } + + public ExportPackageDescription[] getExportPackages() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + return currentData.exportPackages == null ? EMPTY_EXPORTS : currentData.exportPackages; + } + } + + public boolean isResolved() { + return (stateBits & RESOLVED) != 0; + } + + public State getContainingState() { + return containingState; + } + + public BundleDescription[] getFragments() { + if (host != null) + return EMPTY_BUNDLEDESCS; + StateImpl currentState = (StateImpl) getContainingState(); + if (currentState == null) + throw new IllegalStateException("BundleDescription does not belong to a state."); //$NON-NLS-1$ + return currentState.getFragments(this); + } + + public HostSpecification getHost() { + return host; + } + + public boolean isSingleton() { + return (stateBits & SINGLETON) != 0; + } + + public boolean isRemovalPending() { + return (stateBits & REMOVAL_PENDING) != 0; + } + + public boolean hasDynamicImports() { + return (stateBits & HAS_DYNAMICIMPORT) != 0; + } + + public boolean attachFragments() { + return (stateBits & ATTACH_FRAGMENTS) != 0; + } + + public boolean dynamicFragments() { + return (stateBits & DYNAMIC_FRAGMENTS) != 0; + } + + public ExportPackageDescription[] getSelectedExports() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.selectedExports == null) + return EMPTY_EXPORTS; + return currentData.selectedExports; + } + } + + public GenericDescription[] getSelectedGenericCapabilities() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.selectedCapabilities == null) + return EMPTY_GENERICDESCS; + return currentData.selectedCapabilities; + } + } + + public ExportPackageDescription[] getSubstitutedExports() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.substitutedExports == null) + return EMPTY_EXPORTS; + return currentData.substitutedExports; + } + } + + public BundleDescription[] getResolvedRequires() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.resolvedRequires == null) + return EMPTY_BUNDLEDESCS; + return currentData.resolvedRequires; + } + } + + public ExportPackageDescription[] getResolvedImports() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.resolvedImports == null) + return EMPTY_EXPORTS; + return currentData.resolvedImports; + } + } + + public GenericDescription[] getResolvedGenericRequires() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.resolvedCapabilities == null) + return EMPTY_GENERICDESCS; + return currentData.resolvedCapabilities; + } + } + + public Map<String, List<StateWire>> getWires() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.stateWires == null) { + currentData.stateWires = new HashMap<String, List<StateWire>>(0); + } + return currentData.stateWires; + } + } + + Map<String, List<StateWire>> getWiresInternal() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + return currentData.stateWires; + } + } + + protected void setBundleId(long bundleId) { + this.bundleId = bundleId; + } + + protected void setSymbolicName(String symbolicName) { + setName(symbolicName); + } + + protected void setLocation(String location) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.location = location; + } + } + + protected void setPlatformFilter(String platformFilter) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.platformFilter = platformFilter; + } + } + + protected void setExecutionEnvironments(String[] executionEnvironments) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.executionEnvironments = executionEnvironments; + } + } + + protected void setExportPackages(ExportPackageDescription[] exportPackages) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.exportPackages = exportPackages; + if (exportPackages != null) { + for (int i = 0; i < exportPackages.length; i++) { + ((ExportPackageDescriptionImpl) exportPackages[i]).setExporter(this); + } + } + } + } + + protected void setImportPackages(ImportPackageSpecification[] importPackages) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.importPackages = importPackages; + if (importPackages != null) { + for (int i = 0; i < importPackages.length; i++) { + ((ImportPackageSpecificationImpl) importPackages[i]).setBundle(this); + if (ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(importPackages[i].getDirective(Constants.RESOLUTION_DIRECTIVE))) + stateBits |= HAS_DYNAMICIMPORT; + } + } + } + } + + protected void setRequiredBundles(BundleSpecification[] requiredBundles) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.requiredBundles = requiredBundles; + if (requiredBundles != null) + for (int i = 0; i < requiredBundles.length; i++) { + ((VersionConstraintImpl) requiredBundles[i]).setBundle(this); + } + } + } + + protected void setGenericCapabilities(GenericDescription[] genericCapabilities) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.genericCapabilities = genericCapabilities; + if (genericCapabilities != null) + for (int i = 0; i < genericCapabilities.length; i++) + ((GenericDescriptionImpl) genericCapabilities[i]).setSupplier(this); + } + } + + protected void setGenericRequires(GenericSpecification[] genericRequires) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.genericRequires = genericRequires; + if (genericRequires != null) + for (int i = 0; i < genericRequires.length; i++) + ((VersionConstraintImpl) genericRequires[i]).setBundle(this); + } + } + + protected void setNativeCodeSpecification(NativeCodeSpecification nativeCode) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.nativeCode = nativeCode; + if (nativeCode != null) { + ((NativeCodeSpecificationImpl) nativeCode).setBundle(this); + NativeCodeDescription[] suppliers = nativeCode.getPossibleSuppliers(); + if (suppliers != null) + for (int i = 0; i < suppliers.length; i++) + ((NativeCodeDescriptionImpl) suppliers[i]).setSupplier(this); + } + } + } + + protected int getStateBits() { + return stateBits; + } + + protected void setStateBit(int stateBit, boolean on) { + synchronized (this.monitor) { + if (on) { + stateBits |= stateBit; + } else { + stateBits &= ~stateBit; + if (stateBit == RESOLVED) { + if (bundleWiring != null) + bundleWiring.invalidate(); + bundleWiring = null; + } + } + } + } + + protected void setContainingState(State value) { + synchronized (this.monitor) { + containingState = (StateImpl) value; + if (containingState != null && containingState.getReader() != null) { + if (containingState.getReader().isLazyLoaded()) + stateBits |= LAZY_LOADED; + else + stateBits &= ~LAZY_LOADED; + } else { + stateBits &= ~LAZY_LOADED; + } + } + } + + protected void setHost(HostSpecification host) { + synchronized (this.monitor) { + this.host = host; + if (host != null) { + ((VersionConstraintImpl) host).setBundle(this); + } + } + } + + protected void setLazyLoaded(boolean lazyLoad) { + loadLazyData(); + synchronized (this.monitor) { + if (lazyLoad) + stateBits |= LAZY_LOADED; + else + stateBits &= ~LAZY_LOADED; + } + } + + protected void setSelectedExports(ExportPackageDescription[] selectedExports) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.selectedExports = selectedExports; + if (selectedExports != null) { + for (int i = 0; i < selectedExports.length; i++) { + ((ExportPackageDescriptionImpl) selectedExports[i]).setExporter(this); + } + } + } + } + + protected void setSelectedCapabilities(GenericDescription[] selectedCapabilities) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.selectedCapabilities = selectedCapabilities; + if (selectedCapabilities != null) { + for (GenericDescription capability : selectedCapabilities) { + ((GenericDescriptionImpl) capability).setSupplier(this); + } + } + } + } + + protected void setSubstitutedExports(ExportPackageDescription[] substitutedExports) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.substitutedExports = substitutedExports; + } + } + + protected void setResolvedImports(ExportPackageDescription[] resolvedImports) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.resolvedImports = resolvedImports; + } + } + + protected void setResolvedRequires(BundleDescription[] resolvedRequires) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.resolvedRequires = resolvedRequires; + } + } + + protected void setResolvedCapabilities(GenericDescription[] resolvedCapabilities) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.resolvedCapabilities = resolvedCapabilities; + } + } + + protected void setStateWires(Map<String, List<StateWire>> stateWires) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.stateWires = stateWires; + } + } + + void clearAddedDynamicImportPackages() { + synchronized (this.monitor) { + checkLazyData(); + lazyData.addedDynamicImports = null; + } + } + + public String toString() { + if (getSymbolicName() == null) + return "[" + getBundleId() + "]"; //$NON-NLS-1$ //$NON-NLS-2$ + return getSymbolicName() + "_" + getVersion(); //$NON-NLS-1$ + } + + public Object getKey() { + return new Long(bundleId); + } + + public boolean compare(KeyedElement other) { + if (!(other instanceof BundleDescriptionImpl)) + return false; + BundleDescriptionImpl otherBundleDescription = (BundleDescriptionImpl) other; + return bundleId == otherBundleDescription.bundleId; + } + + public int getKeyHashCode() { + return (int) (bundleId ^ (bundleId >>> 32)); + } + + /* TODO Determine if we need more than just Object ID type of hashcode. + public int hashCode() { + if (getSymbolicName() == null) + return (int) (bundleId % Integer.MAX_VALUE); + return (int) ((bundleId * (getSymbolicName().hashCode())) % Integer.MAX_VALUE); + } + */ + + protected void removeDependencies() { + synchronized (this.monitor) { + if (dependencies == null) + return; + Iterator<BundleDescription> iter = dependencies.iterator(); + while (iter.hasNext()) { + ((BundleDescriptionImpl) iter.next()).removeDependent(this); + } + dependencies = null; + } + } + + protected void addDependencies(BaseDescription[] newDependencies, boolean checkDups) { + synchronized (this.monitor) { + if (newDependencies == null) + return; + if (!checkDups && dependencies == null) + dependencies = new ArrayList<BundleDescription>(newDependencies.length); + for (int i = 0; i < newDependencies.length; i++) { + addDependency((BaseDescriptionImpl) newDependencies[i], checkDups); + } + } + } + + protected void addDependency(BaseDescriptionImpl dependency, boolean checkDups) { + synchronized (this.monitor) { + BundleDescriptionImpl bundle = (BundleDescriptionImpl) dependency.getSupplier(); + if (bundle == this) + return; + if (dependencies == null) + dependencies = new ArrayList<BundleDescription>(10); + if (!checkDups || !dependencies.contains(bundle)) { + bundle.addDependent(this); + dependencies.add(bundle); + } + } + } + + /* + * Gets all the bundle dependencies as a result of import-package or require-bundle. + * Self and fragment bundles are removed. + */ + List<BundleDescription> getBundleDependencies() { + synchronized (this.monitor) { + if (dependencies == null) + return new ArrayList<BundleDescription>(0); + ArrayList<BundleDescription> required = new ArrayList<BundleDescription>(dependencies.size()); + for (Iterator<BundleDescription> iter = dependencies.iterator(); iter.hasNext();) { + BundleDescription dep = iter.next(); + if (dep != this && dep.getHost() == null) + required.add(dep); + } + return required; + } + } + + protected void addDependent(BundleDescription dependent) { + synchronized (this.monitor) { + if (dependents == null) + dependents = new ArrayList<BundleDescription>(10); + // no need to check for duplicates here; this is only called in addDepenency which already checks for dups. + dependents.add(dependent); + } + } + + protected void removeDependent(BundleDescription dependent) { + synchronized (this.monitor) { + if (dependents == null) + return; + dependents.remove(dependent); + } + } + + public BundleDescription[] getDependents() { + synchronized (this.monitor) { + if (dependents == null) + return EMPTY_BUNDLEDESCS; + return dependents.toArray(new BundleDescription[dependents.size()]); + } + } + + boolean hasDependents() { + synchronized (this.monitor) { + return dependents == null ? false : dependents.size() > 0; + } + } + + void setFullyLoaded(boolean fullyLoaded) { + synchronized (this.monitor) { + if (fullyLoaded) { + stateBits |= FULLY_LOADED; + } else { + stateBits &= ~FULLY_LOADED; + } + } + } + + boolean isFullyLoaded() { + return (stateBits & FULLY_LOADED) != 0; + } + + void setLazyDataOffset(int lazyDataOffset) { + this.lazyDataOffset = lazyDataOffset; + } + + int getLazyDataOffset() { + return this.lazyDataOffset; + } + + void setLazyDataSize(int lazyDataSize) { + this.lazyDataSize = lazyDataSize; + } + + int getLazyDataSize() { + return this.lazyDataSize; + } + + // DO NOT call while holding this.monitor + private LazyData loadLazyData() { + // TODO add back if ee min 1.2 adds holdsLock method + //if (Thread.holdsLock(this.monitor)) { + // throw new IllegalStateException("Should not call fullyLoad() holding monitor."); //$NON-NLS-1$ + //} + if ((stateBits & LAZY_LOADED) == 0) + return this.lazyData; + + StateImpl currentState = (StateImpl) getContainingState(); + StateReader reader = currentState == null ? null : currentState.getReader(); + if (reader == null) + throw new IllegalStateException("No valid reader for the bundle description"); //$NON-NLS-1$ + + synchronized (currentState.monitor) { + if (isFullyLoaded()) { + reader.setAccessedFlag(true); // set reader accessed flag + return this.lazyData; + } + try { + reader.fullyLoad(this); + return this.lazyData; + } catch (IOException e) { + throw new RuntimeException(e.getMessage(), e); // TODO not sure what to do here!! + } + } + } + + void addDynamicResolvedImport(ExportPackageDescriptionImpl result) { + synchronized (this.monitor) { + // mark the dependency + addDependency(result, true); + // add the export to the list of the resolvedImports + checkLazyData(); + if (lazyData.resolvedImports == null) { + lazyData.resolvedImports = new ExportPackageDescription[] {result}; + return; + } + ExportPackageDescription[] newImports = new ExportPackageDescription[lazyData.resolvedImports.length + 1]; + System.arraycopy(lazyData.resolvedImports, 0, newImports, 0, lazyData.resolvedImports.length); + newImports[newImports.length - 1] = result; + lazyData.resolvedImports = newImports; + } + } + + void addDynamicImportPackages(ImportPackageSpecification[] dynamicImport) { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + if (currentData.addedDynamicImports == null) + currentData.addedDynamicImports = new ArrayList<ImportPackageSpecification>(); + for (ImportPackageSpecification addImport : dynamicImport) { + if (!ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(addImport.getDirective(Constants.RESOLUTION_DIRECTIVE))) + throw new IllegalArgumentException("Import must be a dynamic import."); //$NON-NLS-1$ + } + adding: for (ImportPackageSpecification addImport : dynamicImport) { + for (ImportPackageSpecification currentImport : currentData.addedDynamicImports) { + if (equalImports(addImport, currentImport)) + continue adding; + } + ((ImportPackageSpecificationImpl) addImport).setBundle(this); + currentData.addedDynamicImports.add(addImport); + } + } + } + + private boolean equalImports(ImportPackageSpecification addImport, ImportPackageSpecification currentImport) { + if (!isEqual(addImport.getName(), currentImport.getName())) + return false; + if (!isEqual(addImport.getVersionRange(), currentImport.getVersionRange())) + return false; + if (!isEqual(addImport.getBundleSymbolicName(), currentImport.getBundleSymbolicName())) + return false; + if (!isEqual(addImport.getBundleVersionRange(), currentImport.getBundleVersionRange())) + return false; + return isEqual(addImport.getAttributes(), currentImport.getAttributes()); + } + + private boolean isEqual(Object o1, Object o2) { + return (o1 == null) ? o2 == null : o1.equals(o2); + } + + void unload() { + StateImpl currentState = (StateImpl) getContainingState(); + StateReader reader = currentState == null ? null : currentState.getReader(); + if (reader == null) + throw new IllegalStateException("BundleDescription does not belong to a reader."); //$NON-NLS-1$ + synchronized (currentState.monitor) { + if ((stateBits & LAZY_LOADED) == 0) + return; + if (!isFullyLoaded()) + return; + synchronized (this.monitor) { + setFullyLoaded(false); + lazyData = null; + } + } + } + + void setDynamicStamps(Map<String, Long> dynamicStamps) { + synchronized (this.monitor) { + checkLazyData(); + lazyData.dynamicStamps = dynamicStamps; + } + } + + void setDynamicStamp(String requestedPackage, Long timestamp) { + synchronized (this.monitor) { + checkLazyData(); + if (lazyData.dynamicStamps == null) { + if (timestamp == null) + return; + lazyData.dynamicStamps = new HashMap<String, Long>(); + } + if (timestamp == null) + lazyData.dynamicStamps.remove(requestedPackage); + else + lazyData.dynamicStamps.put(requestedPackage, timestamp); + } + } + + long getDynamicStamp(String requestedPackage) { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + Long stamp = currentData.dynamicStamps == null ? null : (Long) currentData.dynamicStamps.get(requestedPackage); + return stamp == null ? 0 : stamp.longValue(); + } + } + + Map<String, Long> getDynamicStamps() { + LazyData currentData = loadLazyData(); + synchronized (this.monitor) { + return currentData.dynamicStamps; + } + } + + public void setEquinoxEE(int equinox_ee) { + this.equinox_ee = equinox_ee; + } + + public int getEquinoxEE() { + return equinox_ee; + } + + private void checkLazyData() { + if (lazyData == null) + lazyData = new LazyData(); + } + + final class LazyData { + String location; + String platformFilter; + + BundleSpecification[] requiredBundles; + ExportPackageDescription[] exportPackages; + ImportPackageSpecification[] importPackages; + GenericDescription[] genericCapabilities; + GenericSpecification[] genericRequires; + NativeCodeSpecification nativeCode; + + ExportPackageDescription[] selectedExports; + GenericDescription[] selectedCapabilities; + BundleDescription[] resolvedRequires; + ExportPackageDescription[] resolvedImports; + GenericDescription[] resolvedCapabilities; + ExportPackageDescription[] substitutedExports; + String[] executionEnvironments; + + Map<String, Long> dynamicStamps; + Map<String, List<StateWire>> stateWires; + // Note that this is not persisted in the state cache + List<ImportPackageSpecification> addedDynamicImports; + } + + public Map<String, Object> getAttributes() { + synchronized (this.monitor) { + return attributes; + } + } + + @SuppressWarnings("unchecked") + void setAttributes(Map<String, ?> attributes) { + synchronized (this.monitor) { + this.attributes = (Map<String, Object>) attributes; + } + } + + Object getDirective(String key) { + synchronized (this.monitor) { + if (Constants.MANDATORY_DIRECTIVE.equals(key)) + return mandatory; + if (Constants.SINGLETON_DIRECTIVE.equals(key)) + return isSingleton() ? Boolean.TRUE : Boolean.FALSE; + if (Constants.FRAGMENT_ATTACHMENT_DIRECTIVE.equals(key)) { + if (!attachFragments()) + return Constants.FRAGMENT_ATTACHMENT_NEVER; + if (dynamicFragments()) + return Constants.FRAGMENT_ATTACHMENT_ALWAYS; + return Constants.FRAGMENT_ATTACHMENT_RESOLVETIME; + } + } + return null; + } + + void setDirective(String key, Object value) { + // only pay attention to mandatory directive for now; others are set with setState method + if (Constants.MANDATORY_DIRECTIVE.equals(key)) + mandatory = (String[]) value; + } + + @SuppressWarnings("unchecked") + void setArbitraryDirectives(Map<String, ?> directives) { + synchronized (this.monitor) { + this.arbitraryDirectives = (Map<String, String>) directives; + } + } + + Map<String, String> getArbitraryDirectives() { + synchronized (this.monitor) { + return arbitraryDirectives; + } + } + + public Map<String, String> getDeclaredDirectives() { + Map<String, String> result = new HashMap<String, String>(2); + Map<String, String> arbitrary = getArbitraryDirectives(); + if (arbitrary != null) + result.putAll(arbitrary); + if (!attachFragments()) { + result.put(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE, Constants.FRAGMENT_ATTACHMENT_NEVER); + } else { + if (dynamicFragments()) + result.put(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE, Constants.FRAGMENT_ATTACHMENT_ALWAYS); + else + result.put(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE, Constants.FRAGMENT_ATTACHMENT_RESOLVETIME); + } + if (isSingleton()) + result.put(Constants.SINGLETON_DIRECTIVE, Boolean.TRUE.toString()); + String[] mandatoryDirective = (String[]) getDirective(Constants.MANDATORY_DIRECTIVE); + if (mandatoryDirective != null) + result.put(Constants.MANDATORY_DIRECTIVE, ExportPackageDescriptionImpl.toString(mandatoryDirective)); + return Collections.unmodifiableMap(result); + } + + public Map<String, Object> getDeclaredAttributes() { + Map<String, Object> result = new HashMap<String, Object>(1); + synchronized (this.monitor) { + if (attributes != null) + result.putAll(attributes); + } + result.put(BundleRevision.BUNDLE_NAMESPACE, getName()); + result.put(Constants.BUNDLE_VERSION_ATTRIBUTE, getVersion()); + return Collections.unmodifiableMap(result); + } + + public List<BundleRequirement> getDeclaredRequirements(String namespace) { + List<BundleRequirement> result = new ArrayList<BundleRequirement>(); + if (namespace == null || BundleRevision.BUNDLE_NAMESPACE.equals(namespace)) { + BundleSpecification[] requires = getRequiredBundles(); + for (BundleSpecification require : requires) { + result.add(require.getRequirement()); + } + } + if (host != null && (namespace == null || BundleRevision.HOST_NAMESPACE.equals(namespace))) { + result.add(host.getRequirement()); + } + if (namespace == null || BundleRevision.PACKAGE_NAMESPACE.equals(namespace)) { + ImportPackageSpecification[] imports = getImportPackages(); + for (ImportPackageSpecification importPkg : imports) + result.add(importPkg.getRequirement()); + } + GenericSpecification[] genericSpecifications = getGenericRequires(); + for (GenericSpecification requirement : genericSpecifications) { + if (namespace == null || namespace.equals(requirement.getType())) + result.add(requirement.getRequirement()); + } + return Collections.unmodifiableList(result); + } + + public List<BundleCapability> getDeclaredCapabilities(String namespace) { + List<BundleCapability> result = new ArrayList<BundleCapability>(); + if (host == null) { + if (getSymbolicName() != null) { + if (namespace == null || BundleRevision.BUNDLE_NAMESPACE.equals(namespace)) { + result.add(BundleDescriptionImpl.this.getCapability()); + } + if (attachFragments() && (namespace == null || BundleRevision.HOST_NAMESPACE.equals(namespace))) { + result.add(BundleDescriptionImpl.this.getCapability(BundleRevision.HOST_NAMESPACE)); + } + } + + } else { + // may need to have a osgi.wiring.fragment capability + } + if (namespace == null || BundleRevision.PACKAGE_NAMESPACE.equals(namespace)) { + ExportPackageDescription[] exports = getExportPackages(); + for (ExportPackageDescription exportPkg : exports) + result.add(exportPkg.getCapability()); + } + GenericDescription[] genericCapabilities = getGenericCapabilities(); + for (GenericDescription capabilitiy : genericCapabilities) { + if (namespace == null || namespace.equals(capabilitiy.getType())) + result.add(capabilitiy.getCapability()); + } + return Collections.unmodifiableList(result); + } + + public int getTypes() { + return getHost() != null ? BundleRevision.TYPE_FRAGMENT : 0; + } + + public Bundle getBundle() { + Object ref = getUserObject(); + if (ref instanceof BundleReference) + return ((BundleReference) ref).getBundle(); + return null; + } + + String getInternalNameSpace() { + return BundleRevision.BUNDLE_NAMESPACE; + } + + public BundleWiring getWiring() { + synchronized (this.monitor) { + if (bundleWiring != null || !isResolved()) + return bundleWiring; + return bundleWiring = new DescriptionWiring(); + } + } + + static class BundleWireImpl implements BundleWire { + private final BundleCapability capability; + private final BundleWiring provider; + private final BundleRequirement requirement; + private final BundleWiring requirer; + + public BundleWireImpl(StateWire wire) { + VersionConstraint declaredRequirement = wire.getDeclaredRequirement(); + if (declaredRequirement instanceof HostSpecification) + this.capability = ((BaseDescriptionImpl) wire.getDeclaredCapability()).getCapability(BundleRevision.HOST_NAMESPACE); + else + this.capability = wire.getDeclaredCapability().getCapability(); + this.provider = wire.getCapabilityHost().getWiring(); + this.requirement = declaredRequirement.getRequirement(); + this.requirer = wire.getRequirementHost().getWiring(); + } + + public BundleCapability getCapability() { + return capability; + } + + public BundleRequirement getRequirement() { + return requirement; + } + + public BundleWiring getProviderWiring() { + return provider; + } + + public BundleWiring getRequirerWiring() { + return requirer; + } + + @Override + public int hashCode() { + int hashcode = 31 + capability.hashCode(); + hashcode = hashcode * 31 + requirement.hashCode(); + hashcode = hashcode * 31 + provider.hashCode(); + hashcode = hashcode * 31 + requirer.hashCode(); + return hashcode; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof BundleWireImpl)) + return false; + BundleWireImpl other = (BundleWireImpl) obj; + return capability.equals(other.getCapability()) && requirement.equals(other.getRequirement()) && provider.equals(other.getProviderWiring()) && requirer.equals(other.getRequirerWiring()); + } + + public String toString() { + return getRequirement() + " -> " + getCapability(); //$NON-NLS-1$ + } + + public BundleRevision getProvider() { + return provider.getRevision(); + } + + public BundleRevision getRequirer() { + return requirer.getRevision(); + } + } + + /** + * Coerce the generic type of a list from List<BundleWire> + * to List<Wire> + * @param l List to be coerced. + * @return l coerced to List<Wire> + */ + @SuppressWarnings("unchecked") + static List<Wire> asListWire(List<? extends Wire> l) { + return (List<Wire>) l; + } + + /** + * Coerce the generic type of a list from List<BundleCapability> + * to List<Capability> + * @param l List to be coerced. + * @return l coerced to List<Capability> + */ + @SuppressWarnings("unchecked") + static List<Capability> asListCapability(List<? extends Capability> l) { + return (List<Capability>) l; + } + + /** + * Coerce the generic type of a list from List<BundleRequirement> + * to List<Requirement> + * @param l List to be coerced. + * @return l coerced to List<Requirement> + */ + @SuppressWarnings("unchecked") + static List<Requirement> asListRequirement(List<? extends Requirement> l) { + return (List<Requirement>) l; + } + + // Note that description wiring are identity equality based + class DescriptionWiring implements BundleWiring { + private volatile boolean valid = true; + + public Bundle getBundle() { + return BundleDescriptionImpl.this.getBundle(); + } + + public boolean isInUse() { + return valid && (isCurrent() || BundleDescriptionImpl.this.hasDependents()); + } + + void invalidate() { + valid = false; + } + + public boolean isCurrent() { + return valid && !BundleDescriptionImpl.this.isRemovalPending(); + } + + public List<BundleCapability> getCapabilities(String namespace) { + if (!isInUse()) + return null; + List<BundleCapability> result = new ArrayList<BundleCapability>(); + GenericDescription[] genericCapabilities = getSelectedGenericCapabilities(); + for (GenericDescription capabilitiy : genericCapabilities) { + if (namespace == null || namespace.equals(capabilitiy.getType())) + result.add(capabilitiy.getCapability()); + } + if (host != null) + return result; + if (getSymbolicName() != null) { + if (namespace == null || BundleRevision.BUNDLE_NAMESPACE.equals(namespace)) { + result.add(BundleDescriptionImpl.this.getCapability()); + } + if (attachFragments() && (namespace == null || BundleRevision.HOST_NAMESPACE.equals(namespace))) { + result.add(BundleDescriptionImpl.this.getCapability(BundleRevision.HOST_NAMESPACE)); + } + } + if (namespace == null || BundleRevision.PACKAGE_NAMESPACE.equals(namespace)) { + ExportPackageDescription[] exports = getSelectedExports(); + for (ExportPackageDescription exportPkg : exports) + result.add(exportPkg.getCapability()); + } + return result; + } + + public List<Capability> getResourceCapabilities(String namespace) { + return asListCapability(getCapabilities(namespace)); + } + + public List<BundleRequirement> getRequirements(String namespace) { + List<BundleWire> requiredWires = getRequiredWires(namespace); + if (requiredWires == null) + // happens if not in use + return null; + List<BundleRequirement> requirements = new ArrayList<BundleRequirement>(requiredWires.size()); + for (BundleWire wire : requiredWires) { + if (!requirements.contains(wire.getRequirement())) + requirements.add(wire.getRequirement()); + } + // get dynamic imports + if (getHost() == null && (namespace == null || BundleRevision.PACKAGE_NAMESPACE.equals(namespace))) { + // TODO need to handle fragments that add dynamic imports + if (hasDynamicImports()) { + ImportPackageSpecification[] imports = getImportPackages(); + for (ImportPackageSpecification impPackage : imports) { + if (ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(impPackage.getDirective(Constants.RESOLUTION_DIRECTIVE))) { + BundleRequirement req = impPackage.getRequirement(); + if (!requirements.contains(req)) + requirements.add(req); + } + } + } + ImportPackageSpecification[] addedDynamic = getAddedDynamicImportPackages(); + for (ImportPackageSpecification dynamicImport : addedDynamic) { + BundleRequirement req = dynamicImport.getRequirement(); + if (!requirements.contains(req)) + requirements.add(req); + } + } + return requirements; + } + + public List<Requirement> getResourceRequirements(String namespace) { + return asListRequirement(getRequirements(namespace)); + } + + public List<BundleWire> getProvidedWires(String namespace) { + if (!isInUse()) + return null; + BundleDescription[] dependentBundles = getDependents(); + List<BundleWire> unorderedResult = new ArrayList<BundleWire>(); + for (BundleDescription dependent : dependentBundles) { + List<BundleWire> dependentWires = dependent.getWiring().getRequiredWires(namespace); + if (dependentWires != null) + for (BundleWire bundleWire : dependentWires) { + if (bundleWire.getProviderWiring() == this) + unorderedResult.add(bundleWire); + } + } + List<BundleWire> orderedResult = new ArrayList<BundleWire>(unorderedResult.size()); + List<BundleCapability> capabilities = getCapabilities(namespace); + for (BundleCapability capability : capabilities) { + for (Iterator<BundleWire> wires = unorderedResult.iterator(); wires.hasNext();) { + BundleWire wire = wires.next(); + if (wire.getCapability().equals(capability)) { + wires.remove(); + orderedResult.add(wire); + } + } + } + return orderedResult; + } + + public List<Wire> getProvidedResourceWires(String namespace) { + return asListWire(getProvidedWires(namespace)); + } + + public List<BundleWire> getRequiredWires(String namespace) { + if (!isInUse()) + return null; + @SuppressWarnings("unchecked") + List<BundleWire> result = Collections.EMPTY_LIST; + Map<String, List<StateWire>> wireMap = getWires(); + if (namespace == null) { + result = new ArrayList<BundleWire>(); + for (List<StateWire> wires : wireMap.values()) { + for (StateWire wire : wires) { + result.add(new BundleWireImpl(wire)); + } + } + return result; + } + List<StateWire> wires = wireMap.get(namespace); + if (wires == null) + return result; + result = new ArrayList<BundleWire>(wires.size()); + for (StateWire wire : wires) { + result.add(new BundleWireImpl(wire)); + } + return result; + } + + public List<Wire> getRequiredResourceWires(String namespace) { + return asListWire(getRequiredWires(namespace)); + } + + public BundleRevision getRevision() { + return BundleDescriptionImpl.this; + } + + public BundleRevision getResource() { + return getRevision(); + } + + public ClassLoader getClassLoader() { + throw new UnsupportedOperationException(); + } + + + public List<URL> findEntries(String path, String filePattern, int options) { + return null; + } + + public Collection<String> listResources(String path, String filePattern, int options) { + return null; + } + + public String toString() { + return BundleDescriptionImpl.this.toString(); + } + } + + public List<Capability> getCapabilities(String namespace) { + return asListCapability(getDeclaredCapabilities(namespace)); + } + + public List<Requirement> getRequirements(String namespace) { + return asListRequirement(getDeclaredRequirements(namespace)); + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/BundleSpecificationImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/BundleSpecificationImpl.java new file mode 100644 index 000000000..6946f4d7e --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/BundleSpecificationImpl.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import java.util.*; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.Constants; +import org.osgi.framework.wiring.BundleRevision; + +public class BundleSpecificationImpl extends VersionConstraintImpl implements BundleSpecification { + private boolean exported; + private boolean optional; + private Map<String, Object> attributes; + private Map<String, String> arbitraryDirectives; + + protected void setExported(boolean exported) { + synchronized (this.monitor) { + this.exported = exported; + } + } + + protected void setOptional(boolean optional) { + synchronized (this.monitor) { + this.optional = optional; + } + } + + public boolean isExported() { + synchronized (this.monitor) { + return exported; + } + } + + public boolean isOptional() { + synchronized (this.monitor) { + return optional; + } + } + + Map<String, Object> getAttributes() { + synchronized (this.monitor) { + return attributes; + } + } + + @SuppressWarnings("unchecked") + void setAttributes(Map<String, ?> attributes) { + synchronized (this.monitor) { + this.attributes = (Map<String, Object>) attributes; + } + } + + Map<String, String> getArbitraryDirectives() { + synchronized (this.monitor) { + return arbitraryDirectives; + } + } + + @SuppressWarnings("unchecked") + void setArbitraryDirectives(Map<String, ?> directives) { + synchronized (this.monitor) { + this.arbitraryDirectives = (Map<String, String>) directives; + } + } + + public boolean isSatisfiedBy(BaseDescription supplier) { + if (!(supplier instanceof BundleDescriptionImpl)) + return false; + BundleDescriptionImpl candidate = (BundleDescriptionImpl) supplier; + if (candidate.getHost() != null) + return false; + Map<String, ?> requiredAttrs = getAttributes(); + if (requiredAttrs != null) { + Map<String, ?> prividerAttrs = candidate.getAttributes(); + if (prividerAttrs == null) + return false; + for (String key : requiredAttrs.keySet()) { + Object requiredValue = requiredAttrs.get(key); + Object prividedValue = prividerAttrs.get(key); + if (prividedValue == null || !requiredValue.equals(prividedValue)) + return false; + } + } + String[] mandatory = (String[]) candidate.getDirective(Constants.MANDATORY_DIRECTIVE); + if (!hasMandatoryAttributes(mandatory)) + return false; + if (getName() != null && getName().equals(candidate.getSymbolicName()) && (getVersionRange() == null || getVersionRange().isIncluded(candidate.getVersion()))) + return true; + return false; + } + + @Override + protected boolean hasMandatoryAttributes(String[] mandatory) { + if (mandatory != null) { + Map<String, ?> requiredAttrs = getAttributes(); + for (String key : mandatory) { + if (Constants.BUNDLE_VERSION_ATTRIBUTE.equals(key)) + continue; // has a default value of 0.0.0 + if (requiredAttrs == null || requiredAttrs.get(key) == null) + return false; + } + } + return true; + } + + public String toString() { + return "Require-Bundle: " + getName() + "; bundle-version=\"" + getVersionRange() + "\""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + @Override + protected Map<String, String> getInternalDirectives() { + Map<String, String> result = new HashMap<String, String>(2); + synchronized (this.monitor) { + if (arbitraryDirectives != null) + result.putAll(arbitraryDirectives); + if (exported) + result.put(Constants.VISIBILITY_DIRECTIVE, Constants.VISIBILITY_REEXPORT); + if (optional) + result.put(Constants.RESOLUTION_DIRECTIVE, Constants.RESOLUTION_OPTIONAL); + result.put(Constants.FILTER_DIRECTIVE, createFilterDirective()); + return result; + } + } + + private String createFilterDirective() { + StringBuffer filter = new StringBuffer(); + filter.append("(&"); //$NON-NLS-1$ + synchronized (this.monitor) { + addFilterAttribute(filter, BundleRevision.BUNDLE_NAMESPACE, getName()); + VersionRange range = getVersionRange(); + if (range != null && range != VersionRange.emptyRange) + addFilterAttribute(filter, Constants.BUNDLE_VERSION_ATTRIBUTE, range); + if (attributes != null) + addFilterAttributes(filter, attributes); + } + filter.append(')'); + return filter.toString(); + } + + @SuppressWarnings("unchecked") + @Override + protected Map<String, Object> getInteralAttributes() { + return Collections.EMPTY_MAP; + } + + @Override + protected String getInternalNameSpace() { + return BundleRevision.BUNDLE_NAMESPACE; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/ExportPackageDescriptionImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/ExportPackageDescriptionImpl.java new file mode 100644 index 000000000..3f354c3ea --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/ExportPackageDescriptionImpl.java @@ -0,0 +1,239 @@ +/******************************************************************************* + * Copyright (c) 2004, 2011 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ + +package org.eclipse.osgi.internal.resolver; + +import java.util.*; + +import org.eclipse.osgi.internal.framework.EquinoxContainer; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; +import org.osgi.framework.wiring.BundleRevision; + +public class ExportPackageDescriptionImpl extends BaseDescriptionImpl implements ExportPackageDescription { + public static final String EQUINOX_EE = "x-equinox-ee"; //$NON-NLS-1$ + private static final Integer EQUINOX_EE_DEFAULT = new Integer(-1); + private String[] uses; + private Map<String, Object> attributes; + private Map<String, String> arbitraryDirectives; + private volatile BundleDescription exporter; + private String exclude; + private String include; + private String[] friends; + private String[] mandatory; + private Boolean internal = Boolean.FALSE; + private int equinox_ee = -1; + private ExportPackageDescription fragmentDeclaration = null; + + public ExportPackageDescriptionImpl() { + super(); + } + + public ExportPackageDescriptionImpl(BundleDescription host, ExportPackageDescription fragmentDeclaration) { + setName(fragmentDeclaration.getName()); + setVersion(fragmentDeclaration.getVersion()); + setDirectives(fragmentDeclaration.getDirectives()); + setArbitraryDirectives(((ExportPackageDescriptionImpl) fragmentDeclaration).getArbitraryDirectives()); + setAttributes(fragmentDeclaration.getAttributes()); + setExporter(host); + this.fragmentDeclaration = fragmentDeclaration; + } + + public Map<String, Object> getDirectives() { + synchronized (this.monitor) { + Map<String, Object> result = new HashMap<String, Object>(7); + if (uses != null) + result.put(Constants.USES_DIRECTIVE, uses); + if (exclude != null) + result.put(Constants.EXCLUDE_DIRECTIVE, exclude); + if (include != null) + result.put(Constants.INCLUDE_DIRECTIVE, include); + if (mandatory != null) + result.put(Constants.MANDATORY_DIRECTIVE, mandatory); + if (friends != null) + result.put(StateImpl.FRIENDS_DIRECTIVE, friends); + result.put(StateImpl.INTERNAL_DIRECTIVE, internal); + result.put(EQUINOX_EE, equinox_ee == -1 ? EQUINOX_EE_DEFAULT : new Integer(equinox_ee)); + return result; + } + } + + public Map<String, String> getDeclaredDirectives() { + Map<String, String> result = new HashMap<String, String>(6); + synchronized (this.monitor) { + Map<String, String> arbitrary = getArbitraryDirectives(); + if (arbitrary != null) + result.putAll(arbitrary); + if (uses != null) + result.put(Constants.USES_DIRECTIVE, toString(uses)); + if (exclude != null) + result.put(Constants.EXCLUDE_DIRECTIVE, exclude); + if (include != null) + result.put(Constants.INCLUDE_DIRECTIVE, include); + if (mandatory != null) + result.put(Constants.MANDATORY_DIRECTIVE, toString(mandatory)); + if (friends != null) + result.put(StateImpl.FRIENDS_DIRECTIVE, toString(friends)); + if (internal != null) + result.put(StateImpl.INTERNAL_DIRECTIVE, internal.toString()); + return Collections.unmodifiableMap(result); + } + } + + public Map<String, Object> getDeclaredAttributes() { + Map<String, Object> result = new HashMap<String, Object>(2); + synchronized (this.monitor) { + if (attributes != null) + result.putAll(attributes); + result.put(BundleRevision.PACKAGE_NAMESPACE, getName()); + result.put(Constants.VERSION_ATTRIBUTE, getVersion()); + Version bundleVersion = getSupplier().getVersion(); + if (bundleVersion != null) + result.put(Constants.BUNDLE_VERSION_ATTRIBUTE, bundleVersion); + String symbolicName = getSupplier().getSymbolicName(); + if (symbolicName != null) { + if (symbolicName.equals(EquinoxContainer.NAME)) + result.put(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, Arrays.asList(Constants.SYSTEM_BUNDLE_SYMBOLICNAME, symbolicName)); + else + result.put(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, symbolicName); + } + return Collections.unmodifiableMap(result); + } + } + + static String toString(String[] list) { + StringBuffer buffer = new StringBuffer(); + for (String string : list) + buffer.append(string).append(','); + if (buffer.length() > 0) + buffer.setLength(buffer.length() - 1); + return buffer.toString(); + } + + public Object getDirective(String key) { + synchronized (this.monitor) { + if (key.equals(Constants.USES_DIRECTIVE)) + return uses; + if (key.equals(Constants.EXCLUDE_DIRECTIVE)) + return exclude; + if (key.equals(Constants.INCLUDE_DIRECTIVE)) + return include; + if (key.equals(Constants.MANDATORY_DIRECTIVE)) + return mandatory; + if (key.equals(StateImpl.FRIENDS_DIRECTIVE)) + return friends; + if (key.equals(StateImpl.INTERNAL_DIRECTIVE)) + return internal; + if (key.equals(EQUINOX_EE)) + return equinox_ee == -1 ? EQUINOX_EE_DEFAULT : new Integer(equinox_ee); + return null; + } + } + + public Object setDirective(String key, Object value) { + synchronized (this.monitor) { + if (key.equals(Constants.USES_DIRECTIVE)) + return uses = (String[]) value; + if (key.equals(Constants.EXCLUDE_DIRECTIVE)) + return exclude = (String) value; + if (key.equals(Constants.INCLUDE_DIRECTIVE)) + return include = (String) value; + if (key.equals(Constants.MANDATORY_DIRECTIVE)) + return mandatory = (String[]) value; + if (key.equals(StateImpl.FRIENDS_DIRECTIVE)) + return friends = (String[]) value; + if (key.equals(StateImpl.INTERNAL_DIRECTIVE)) + return internal = (Boolean) value; + if (key.equals(EQUINOX_EE)) { + equinox_ee = ((Integer) value).intValue(); + return value; + } + return null; + } + } + + public void setDirectives(Map<String, ?> directives) { + synchronized (this.monitor) { + if (directives == null) + return; + uses = (String[]) directives.get(Constants.USES_DIRECTIVE); + exclude = (String) directives.get(Constants.EXCLUDE_DIRECTIVE); + include = (String) directives.get(Constants.INCLUDE_DIRECTIVE); + mandatory = (String[]) directives.get(Constants.MANDATORY_DIRECTIVE); + friends = (String[]) directives.get(StateImpl.FRIENDS_DIRECTIVE); + internal = (Boolean) directives.get(StateImpl.INTERNAL_DIRECTIVE); + equinox_ee = ((Integer) directives.get(EQUINOX_EE)).intValue(); + } + } + + @SuppressWarnings("unchecked") + void setArbitraryDirectives(Map<String, ?> directives) { + synchronized (this.monitor) { + this.arbitraryDirectives = (Map<String, String>) directives; + } + } + + Map<String, String> getArbitraryDirectives() { + synchronized (this.monitor) { + return arbitraryDirectives; + } + } + + public Map<String, Object> getAttributes() { + synchronized (this.monitor) { + return attributes; + } + } + + public BundleDescription getSupplier() { + return getExporter(); + } + + public BundleDescription getExporter() { + return exporter; + } + + /** + * @deprecated + */ + public boolean isRoot() { + return true; + } + + @SuppressWarnings("unchecked") + protected void setAttributes(Map<String, ?> attributes) { + synchronized (this.monitor) { + this.attributes = (Map<String, Object>) attributes; + } + } + + protected void setExporter(BundleDescription exporter) { + this.exporter = exporter; + } + + public BaseDescription getFragmentDeclaration() { + return fragmentDeclaration; + } + + void setFragmentDeclaration(ExportPackageDescription fragmentDeclaration) { + this.fragmentDeclaration = fragmentDeclaration; + } + + public String toString() { + return "Export-Package: " + getName() + "; version=\"" + getVersion() + "\""; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + } + + String getInternalNameSpace() { + return BundleRevision.PACKAGE_NAMESPACE; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/GenericDescriptionImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/GenericDescriptionImpl.java new file mode 100644 index 000000000..9e438f033 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/GenericDescriptionImpl.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2006, 2011 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import java.util.*; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; + +public class GenericDescriptionImpl extends BaseDescriptionImpl implements GenericDescription { + private Dictionary<String, Object> attributes; + private volatile BundleDescription supplier; + private volatile String type = GenericDescription.DEFAULT_TYPE; + private Map<String, String> directives; + private GenericDescription fragmentDeclaration; + + public GenericDescriptionImpl() { + super(); + } + + public GenericDescriptionImpl(BundleDescription host, GenericDescription fragmentDeclaration) { + setType(fragmentDeclaration.getType()); + Dictionary<String, Object> origAttrs = fragmentDeclaration.getAttributes(); + if (origAttrs != null) { + Hashtable<String, Object> copyAttrs = new Hashtable<String, Object>(); + for (Enumeration<String> keys = origAttrs.keys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + copyAttrs.put(key, origAttrs.get(key)); + } + setAttributes(copyAttrs); + } + Map<String, String> origDirectives = fragmentDeclaration.getDeclaredDirectives(); + Map<String, String> copyDirectives = new HashMap<String, String>(origDirectives); + setDirectives(copyDirectives); + setSupplier(host); + this.fragmentDeclaration = fragmentDeclaration; + } + + public Dictionary<String, Object> getAttributes() { + synchronized (this.monitor) { + return attributes; + } + } + + public BundleDescription getSupplier() { + return supplier; + } + + void setAttributes(Dictionary<String, Object> attributes) { + synchronized (this.monitor) { + this.attributes = attributes; + } + } + + void setDirectives(Map<String, String> directives) { + synchronized (this.monitor) { + this.directives = directives; + } + } + + void setSupplier(BundleDescription supplier) { + this.supplier = supplier; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(Constants.PROVIDE_CAPABILITY).append(": ").append(getType()); //$NON-NLS-1$ + Map<String, Object> attrs = getDeclaredAttributes(); + sb.append(toString(attrs, false)); + return sb.toString(); + } + + /** + * @deprecated + */ + public String getName() { + synchronized (this.monitor) { + Object name = attributes != null ? attributes.get(getType()) : null; + return name instanceof String ? (String) name : null; + } + } + + public String getType() { + return type; + } + + void setType(String type) { + if (type == null || type.equals(GenericDescription.DEFAULT_TYPE)) + this.type = GenericDescription.DEFAULT_TYPE; + else + this.type = type; + } + + /** + * @deprecated + */ + public Version getVersion() { + Object version = attributes != null ? attributes.get(Constants.VERSION_ATTRIBUTE) : null; + return version instanceof Version ? (Version) version : super.getVersion(); + } + + @SuppressWarnings("unchecked") + public Map<String, String> getDeclaredDirectives() { + synchronized (this.monitor) { + if (directives == null) + return Collections.EMPTY_MAP; + return Collections.unmodifiableMap(directives); + } + } + + public Map<String, Object> getDeclaredAttributes() { + synchronized (this.monitor) { + Map<String, Object> result = new HashMap<String, Object>(5); + if (attributes != null) + for (Enumeration<String> keys = attributes.keys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + Object value = attributes.get(key); + if (value instanceof List) + value = Collections.unmodifiableList((List<Object>) value); + result.put(key, value); + } + return Collections.unmodifiableMap(result); + } + } + + String getInternalNameSpace() { + return getType(); + } + + public BaseDescription getFragmentDeclaration() { + return fragmentDeclaration; + } + + void setFragmentDeclaration(GenericDescription fragmentDeclaration) { + this.fragmentDeclaration = fragmentDeclaration; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/GenericSpecificationImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/GenericSpecificationImpl.java new file mode 100644 index 000000000..f0a30cbd2 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/GenericSpecificationImpl.java @@ -0,0 +1,200 @@ +/******************************************************************************* + * Copyright (c) 2006, 2012 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import org.eclipse.osgi.internal.framework.FilterImpl; + +import java.util.*; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.*; +import org.osgi.resource.Namespace; + +public class GenericSpecificationImpl extends VersionConstraintImpl implements GenericSpecification { + private Filter matchingFilter; + private String type = GenericDescription.DEFAULT_TYPE; + private int resolution = 0; + private GenericDescription[] suppliers; + private Map<String, Object> attributes; + private Map<String, String> arbitraryDirectives; + /* + * Indicates that a generic constraint was from converting the BREE header + */ + public static final int RESOLUTION_FROM_BREE = 0x04; + + public String getMatchingFilter() { + synchronized (this.monitor) { + return matchingFilter == null ? null : matchingFilter.toString(); + } + } + + void setMatchingFilter(String matchingFilter, boolean matchName) throws InvalidSyntaxException { + synchronized (this.monitor) { + String name = getName(); + if (matchName && name != null && !"*".equals(name)) { //$NON-NLS-1$ + String nameFilter = "(" + getType() + "=" + getName() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + matchingFilter = matchingFilter == null ? nameFilter : "(&" + nameFilter + matchingFilter + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } + this.matchingFilter = matchingFilter == null ? null : FilterImpl.newInstance(matchingFilter); + } + } + + void setMatchingFilter(Filter matchingFilter) { + synchronized (this.monitor) { + this.matchingFilter = matchingFilter; + } + } + + public boolean isSatisfiedBy(BaseDescription supplier) { + if (!(supplier instanceof GenericDescription)) + return false; + GenericDescription candidate = (GenericDescription) supplier; + if (!getType().equals(candidate.getType())) + return false; + // Note that names and versions are only matched by including them in the filter + return matchingFilter == null || matchingFilter.match(candidate.getAttributes()); + } + + @Override + protected boolean hasMandatoryAttributes(String[] mandatory) { + return true; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(Constants.REQUIRE_CAPABILITY).append(": ").append(getType()); //$NON-NLS-1$ + if (matchingFilter != null) + sb.append("; filter=\"").append(getMatchingFilter()).append('"'); //$NON-NLS-1$ + return sb.toString(); + } + + public String getType() { + synchronized (this.monitor) { + return type; + } + } + + void setType(String type) { + synchronized (this.monitor) { + if (type == null || type.equals(GenericDescription.DEFAULT_TYPE)) + this.type = GenericDescription.DEFAULT_TYPE; + else + this.type = type; + } + } + + public int getResolution() { + synchronized (this.monitor) { + return resolution; + } + } + + public boolean isResolved() { + synchronized (this.monitor) { + return suppliers != null && suppliers.length > 0; + } + } + + void setResolution(int resolution) { + synchronized (this.monitor) { + this.resolution = resolution; + } + } + + public BaseDescription getSupplier() { + synchronized (this.monitor) { + return suppliers == null || suppliers.length == 0 ? null : suppliers[0]; + } + } + + protected void setSupplier(BaseDescription supplier) { + synchronized (this.monitor) { + if (supplier == null) { + suppliers = null; + return; + } + int len = suppliers == null ? 0 : suppliers.length; + GenericDescription[] temp = new GenericDescription[len + 1]; + if (suppliers != null) + System.arraycopy(suppliers, 0, temp, 0, len); + temp[len] = (GenericDescription) supplier; + suppliers = temp; + } + } + + public GenericDescription[] getSuppliers() { + synchronized (this.monitor) { + return suppliers; + } + } + + void setSupplers(GenericDescription[] suppliers) { + synchronized (this.monitor) { + this.suppliers = suppliers; + } + } + + Map<String, Object> getAttributes() { + synchronized (this.monitor) { + return attributes; + } + } + + @SuppressWarnings("unchecked") + void setAttributes(Map<String, ?> attributes) { + synchronized (this.monitor) { + this.attributes = (Map<String, Object>) attributes; + } + } + + Map<String, String> getArbitraryDirectives() { + synchronized (this.monitor) { + return arbitraryDirectives; + } + } + + @SuppressWarnings("unchecked") + void setArbitraryDirectives(Map<String, ?> directives) { + synchronized (this.monitor) { + this.arbitraryDirectives = (Map<String, String>) directives; + } + } + + @Override + protected Map<String, String> getInternalDirectives() { + Map<String, String> result = new HashMap<String, String>(2); + synchronized (this.monitor) { + if (arbitraryDirectives != null) + result.putAll(arbitraryDirectives); + if ((resolution & GenericSpecification.RESOLUTION_OPTIONAL) != 0) + result.put(Constants.RESOLUTION_DIRECTIVE, Constants.RESOLUTION_OPTIONAL); + if ((resolution & GenericSpecification.RESOLUTION_MULTIPLE) != 0) + result.put(Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE, Namespace.CARDINALITY_MULTIPLE); + if (matchingFilter != null) { + result.put(Constants.FILTER_DIRECTIVE, matchingFilter.toString()); + } + } + return result; + } + + @SuppressWarnings("unchecked") + @Override + protected Map<String, Object> getInteralAttributes() { + synchronized (this.monitor) { + return attributes == null ? Collections.EMPTY_MAP : new HashMap<String, Object>(attributes); + } + } + + @Override + protected String getInternalNameSpace() { + return getType(); + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/HostSpecificationImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/HostSpecificationImpl.java new file mode 100644 index 000000000..2f6d77841 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/HostSpecificationImpl.java @@ -0,0 +1,174 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ + +package org.eclipse.osgi.internal.resolver; + +import java.util.*; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.Constants; +import org.osgi.framework.wiring.BundleRevision; + +public class HostSpecificationImpl extends VersionConstraintImpl implements HostSpecification { + + private BundleDescription[] hosts; + private boolean multihost = false; + private Map<String, Object> attributes; + private Map<String, String> arbitraryDirectives; + + Map<String, Object> getAttributes() { + synchronized (this.monitor) { + return attributes; + } + } + + @SuppressWarnings("unchecked") + void setAttributes(Map<String, ?> attributes) { + synchronized (this.monitor) { + this.attributes = (Map<String, Object>) attributes; + } + } + + Map<String, String> getArbitraryDirectives() { + synchronized (this.monitor) { + return arbitraryDirectives; + } + } + + @SuppressWarnings("unchecked") + void setArbitraryDirectives(Map<String, ?> directives) { + synchronized (this.monitor) { + this.arbitraryDirectives = (Map<String, String>) directives; + } + } + + public boolean isSatisfiedBy(BaseDescription supplier) { + if (!(supplier instanceof BundleDescriptionImpl)) + return false; + BundleDescriptionImpl candidate = (BundleDescriptionImpl) supplier; + if (candidate.getHost() != null) + return false; + Map<String, ?> requiredAttrs = getAttributes(); + if (requiredAttrs != null) { + Map<String, ?> prividerAttrs = candidate.getAttributes(); + if (prividerAttrs == null) + return false; + for (String key : requiredAttrs.keySet()) { + Object requiredValue = requiredAttrs.get(key); + Object prividedValue = prividerAttrs.get(key); + if (prividedValue == null || !requiredValue.equals(prividedValue)) + return false; + } + } + String[] mandatory = (String[]) candidate.getDirective(Constants.MANDATORY_DIRECTIVE); + if (!hasMandatoryAttributes(mandatory)) + return false; + if (getName() != null && getName().equals(candidate.getSymbolicName()) && (getVersionRange() == null || getVersionRange().isIncluded(candidate.getVersion()))) + return true; + return false; + } + + public BundleDescription[] getHosts() { + synchronized (this.monitor) { + return hosts == null ? BundleDescriptionImpl.EMPTY_BUNDLEDESCS : hosts; + } + } + + @Override + protected boolean hasMandatoryAttributes(String[] mandatory) { + if (mandatory != null) { + Map<String, ?> requiredAttrs = getAttributes(); + for (String key : mandatory) { + if (Constants.BUNDLE_VERSION_ATTRIBUTE.equals(key)) + continue; // has a default value of 0.0.0 + if (requiredAttrs == null || requiredAttrs.get(key) == null) + return false; + } + } + return true; + } + + public boolean isResolved() { + synchronized (this.monitor) { + return hosts != null && hosts.length > 0; + } + } + + /* + * The resolve algorithm will call this method to set the hosts. + */ + void setHosts(BundleDescription[] hosts) { + synchronized (this.monitor) { + this.hosts = hosts; + } + } + + public String toString() { + return "Fragment-Host: " + getName() + "; bundle-version=\"" + getVersionRange() + "\""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + public BaseDescription getSupplier() { + synchronized (this.monitor) { + if (hosts == null || hosts.length == 0) + return null; + return hosts[0]; + } + } + + public boolean isMultiHost() { + synchronized (this.monitor) { + return multihost; + } + } + + void setIsMultiHost(boolean multihost) { + synchronized (this.monitor) { + this.multihost = multihost; + } + } + + @Override + protected Map<String, String> getInternalDirectives() { + Map<String, String> result = new HashMap<String, String>(2); + synchronized (this.monitor) { + if (arbitraryDirectives != null) + result.putAll(arbitraryDirectives); + result.put(Constants.FILTER_DIRECTIVE, createFilterDirective()); + return result; + } + } + + private String createFilterDirective() { + StringBuffer filter = new StringBuffer(); + filter.append("(&"); //$NON-NLS-1$ + synchronized (this.monitor) { + addFilterAttribute(filter, BundleRevision.HOST_NAMESPACE, getName()); + VersionRange range = getVersionRange(); + if (range != null && range != VersionRange.emptyRange) + addFilterAttribute(filter, Constants.BUNDLE_VERSION_ATTRIBUTE, range); + if (attributes != null) + addFilterAttributes(filter, attributes); + } + filter.append(')'); + return filter.toString(); + } + + @SuppressWarnings("unchecked") + @Override + protected Map<String, Object> getInteralAttributes() { + return Collections.EMPTY_MAP; + } + + @Override + protected String getInternalNameSpace() { + return BundleRevision.HOST_NAMESPACE; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/ImportPackageSpecificationImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/ImportPackageSpecificationImpl.java new file mode 100644 index 000000000..5e82d40da --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/ImportPackageSpecificationImpl.java @@ -0,0 +1,263 @@ +/******************************************************************************* + * Copyright (c) 2003, 2012 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 + * Danail Nachev - ProSyst - bug 218625 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import java.util.*; + +import org.eclipse.osgi.internal.framework.EquinoxContainer; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.Constants; +import org.osgi.framework.wiring.BundleRevision; + +public class ImportPackageSpecificationImpl extends VersionConstraintImpl implements ImportPackageSpecification { + private String resolution = ImportPackageSpecification.RESOLUTION_STATIC; // the default is static + private String symbolicName; + private VersionRange bundleVersionRange; + private Map<String, Object> attributes; + private Map<String, String> arbitraryDirectives; + + public Map<String, Object> getDirectives() { + synchronized (this.monitor) { + Map<String, Object> result = new HashMap<String, Object>(5); + if (resolution != null) + result.put(Constants.RESOLUTION_DIRECTIVE, resolution); + return result; + } + } + + public Object getDirective(String key) { + synchronized (this.monitor) { + if (key.equals(Constants.RESOLUTION_DIRECTIVE)) + return resolution; + return null; + } + } + + Object setDirective(String key, Object value) { + synchronized (this.monitor) { + if (key.equals(Constants.RESOLUTION_DIRECTIVE)) + return resolution = (String) value; + return null; + } + } + + void setDirectives(Map<String, ?> directives) { + synchronized (this.monitor) { + if (directives == null) + return; + resolution = (String) directives.get(Constants.RESOLUTION_DIRECTIVE); + } + } + + @SuppressWarnings("unchecked") + void setArbitraryDirectives(Map<String, ?> directives) { + synchronized (this.monitor) { + this.arbitraryDirectives = (Map<String, String>) directives; + } + } + + Map<String, String> getArbitraryDirectives() { + synchronized (this.monitor) { + return arbitraryDirectives; + } + } + + public String getBundleSymbolicName() { + synchronized (this.monitor) { + if (Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(symbolicName)) { + StateImpl state = (StateImpl) getBundle().getContainingState(); + return state == null ? EquinoxContainer.NAME : state.getSystemBundle(); + } + return symbolicName; + } + } + + public VersionRange getBundleVersionRange() { + synchronized (this.monitor) { + if (bundleVersionRange == null) + return VersionRange.emptyRange; + return bundleVersionRange; + } + } + + public Map<String, Object> getAttributes() { + synchronized (this.monitor) { + return attributes; + } + } + + public boolean isSatisfiedBy(BaseDescription supplier) { + return isSatisfiedBy(supplier, true); + } + + public boolean isSatisfiedBy(BaseDescription supplier, boolean checkEE) { + if (!(supplier instanceof ExportPackageDescription)) + return false; + ExportPackageDescriptionImpl pkgDes = (ExportPackageDescriptionImpl) supplier; + + // If we are in strict mode, check to see if the export specifies friends. + // If it does, are we one of the friends + String[] friends = (String[]) pkgDes.getDirective(StateImpl.FRIENDS_DIRECTIVE); + Boolean internal = (Boolean) pkgDes.getDirective(StateImpl.INTERNAL_DIRECTIVE); + if (internal.booleanValue() || friends != null) { + StateImpl state = (StateImpl) getBundle().getContainingState(); + boolean strict = state == null ? false : state.inStrictMode(); + if (strict) { + if (internal.booleanValue()) + return false; + boolean found = false; + if (friends != null && getBundle().getSymbolicName() != null) + for (int i = 0; i < friends.length; i++) + if (getBundle().getSymbolicName().equals(friends[i])) + found = true; + if (!found) + return false; + } + } + String exporterSymbolicName = getBundleSymbolicName(); + if (exporterSymbolicName != null) { + BundleDescription exporter = pkgDes.getExporter(); + if (!exporterSymbolicName.equals(exporter.getSymbolicName())) + return false; + if (getBundleVersionRange() != null && !getBundleVersionRange().isIncluded(exporter.getVersion())) + return false; + } + + String name = getName(); + // shortcut '*' + // NOTE: wildcards are supported only in cases where this is a dynamic import + if (!"*".equals(name) && !(name.endsWith(".*") && pkgDes.getName().startsWith(name.substring(0, name.length() - 1))) && !pkgDes.getName().equals(name)) //$NON-NLS-1$ //$NON-NLS-2$ + return false; + if (getVersionRange() != null && !getVersionRange().isIncluded(pkgDes.getVersion())) + return false; + + Map<String, ?> importAttrs = getAttributes(); + if (importAttrs != null) { + Map<String, ?> exportAttrs = pkgDes.getAttributes(); + if (exportAttrs == null) + return false; + for (Iterator<String> i = importAttrs.keySet().iterator(); i.hasNext();) { + String importKey = i.next(); + Object importValue = importAttrs.get(importKey); + Object exportValue = exportAttrs.get(importKey); + if (exportValue == null || !importValue.equals(exportValue)) + return false; + } + } + String[] mandatory = (String[]) pkgDes.getDirective(Constants.MANDATORY_DIRECTIVE); + if (!hasMandatoryAttributes(mandatory)) + return false; + // finally check the ee index + if (!checkEE) + return true; + if (((BundleDescriptionImpl) getBundle()).getEquinoxEE() < 0) + return true; + int eeIndex = ((Integer) pkgDes.getDirective(ExportPackageDescriptionImpl.EQUINOX_EE)).intValue(); + return eeIndex < 0 || eeIndex == ((BundleDescriptionImpl) getBundle()).getEquinoxEE(); + } + + @Override + protected boolean hasMandatoryAttributes(String[] mandatory) { + if (mandatory != null) { + Map<String, ?> importAttrs = getAttributes(); + for (int i = 0; i < mandatory.length; i++) { + if (Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE.equals(mandatory[i])) { + if (getBundleSymbolicName() == null) + return false; + } else if (Constants.BUNDLE_VERSION_ATTRIBUTE.equals(mandatory[i])) { + if (bundleVersionRange == null) + return false; + } else if (Constants.PACKAGE_SPECIFICATION_VERSION.equals(mandatory[i]) || Constants.VERSION_ATTRIBUTE.equals(mandatory[i])) { + if (getVersionRange() == null) + return false; + } else { // arbitrary attribute + if (importAttrs == null) + return false; + if (importAttrs.get(mandatory[i]) == null) + return false; + } + } + } + return true; + } + + protected void setBundleSymbolicName(String symbolicName) { + synchronized (this.monitor) { + this.symbolicName = symbolicName; + } + } + + protected void setBundleVersionRange(VersionRange bundleVersionRange) { + synchronized (this.monitor) { + this.bundleVersionRange = bundleVersionRange; + } + } + + @SuppressWarnings("unchecked") + protected void setAttributes(Map<String, ?> attributes) { + synchronized (this.monitor) { + this.attributes = (Map<String, Object>) attributes; + } + } + + public String toString() { + return "Import-Package: " + getName() + "; version=\"" + getVersionRange() + "\""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + @Override + protected Map<String, String> getInternalDirectives() { + synchronized (this.monitor) { + Map<String, String> result = new HashMap<String, String>(5); + if (arbitraryDirectives != null) + result.putAll(arbitraryDirectives); + if (resolution != null) { + if (ImportPackageSpecification.RESOLUTION_STATIC.equals(resolution)) + result.put(Constants.RESOLUTION_DIRECTIVE, Constants.RESOLUTION_MANDATORY); + else + result.put(Constants.RESOLUTION_DIRECTIVE, resolution); + } + result.put(Constants.FILTER_DIRECTIVE, createFilterDirective()); + return result; + } + } + + private String createFilterDirective() { + StringBuffer filter = new StringBuffer(); + filter.append("(&"); //$NON-NLS-1$ + synchronized (this.monitor) { + addFilterAttribute(filter, BundleRevision.PACKAGE_NAMESPACE, getName(), false); + VersionRange range = getVersionRange(); + if (range != null && range != VersionRange.emptyRange) + addFilterAttribute(filter, Constants.VERSION_ATTRIBUTE, range); + if (symbolicName != null) + addFilterAttribute(filter, Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, symbolicName); + if (bundleVersionRange != null) + addFilterAttribute(filter, Constants.BUNDLE_VERSION_ATTRIBUTE, bundleVersionRange); + if (attributes != null) + addFilterAttributes(filter, attributes); + } + filter.append(')'); + return filter.toString(); + } + + @SuppressWarnings("unchecked") + @Override + protected Map<String, Object> getInteralAttributes() { + return Collections.EMPTY_MAP; + } + + @Override + protected String getInternalNameSpace() { + return BundleRevision.PACKAGE_NAMESPACE; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/NativeCodeDescriptionImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/NativeCodeDescriptionImpl.java new file mode 100644 index 000000000..a9c183ec1 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/NativeCodeDescriptionImpl.java @@ -0,0 +1,220 @@ +/******************************************************************************* + * Copyright (c) 2007, 2011 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import org.eclipse.osgi.internal.framework.FilterImpl; + +import java.util.*; +import org.eclipse.osgi.service.resolver.*; +import org.eclipse.osgi.service.resolver.VersionRange; +import org.osgi.framework.*; + +public class NativeCodeDescriptionImpl extends BaseDescriptionImpl implements NativeCodeDescription { + private static final VersionRange[] EMPTY_VERSIONRANGES = new VersionRange[0]; + + private volatile Filter filter; + private String[] languages; + private String[] nativePaths; + private String[] osNames; + private VersionRange[] osVersions; + private String[] processors; + private BundleDescription supplier; + private volatile boolean invalidNativePaths = false; + + public Filter getFilter() { + return filter; + } + + public String[] getLanguages() { + synchronized (this.monitor) { + if (languages == null) + return BundleDescriptionImpl.EMPTY_STRING; + return languages; + } + } + + public String[] getNativePaths() { + synchronized (this.monitor) { + if (nativePaths == null) + return BundleDescriptionImpl.EMPTY_STRING; + return nativePaths; + } + } + + public String[] getOSNames() { + synchronized (this.monitor) { + if (osNames == null) + return BundleDescriptionImpl.EMPTY_STRING; + return osNames; + } + } + + public VersionRange[] getOSVersions() { + synchronized (this.monitor) { + if (osVersions == null) + return EMPTY_VERSIONRANGES; + return osVersions; + } + } + + public String[] getProcessors() { + synchronized (this.monitor) { + if (processors == null) + return BundleDescriptionImpl.EMPTY_STRING; + return processors; + } + } + + public BundleDescription getSupplier() { + return supplier; + } + + public int compareTo(NativeCodeDescription otherDesc) { + State containingState = getSupplier().getContainingState(); + if (containingState == null) + return 0; + Dictionary<Object, Object>[] platformProps = containingState.getPlatformProperties(); + Version osversion; + try { + osversion = Version.parseVersion((String) platformProps[0].get(Constants.FRAMEWORK_OS_VERSION)); + } catch (Exception e) { + osversion = Version.emptyVersion; + } + VersionRange[] thisRanges = getOSVersions(); + VersionRange[] otherRanges = otherDesc.getOSVersions(); + Version thisHighest = getHighestVersionMatch(osversion, thisRanges); + Version otherHighest = getHighestVersionMatch(osversion, otherRanges); + if (thisHighest.compareTo(otherHighest) < 0) + return -1; + return (getLanguages().length == 0 ? 0 : 1) - (otherDesc.getLanguages().length == 0 ? 0 : 1); + } + + public boolean hasInvalidNativePaths() { + return invalidNativePaths; + } + + private Version getHighestVersionMatch(Version version, VersionRange[] ranges) { + Version highest = Version.emptyVersion; + for (int i = 0; i < ranges.length; i++) { + if (ranges[i].isIncluded(version) && highest.compareTo(ranges[i].getMinimum()) < 0) + highest = ranges[i].getMinimum(); + } + return highest; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + + String[] paths = getNativePaths(); + for (int i = 0; i < paths.length; i++) { + if (i > 0) { + sb.append("; "); //$NON-NLS-1$ + } + sb.append(paths[i]); + } + + String[] procs = getProcessors(); + for (int i = 0; i < procs.length; i++) { + sb.append("; "); //$NON-NLS-1$ + sb.append(Constants.BUNDLE_NATIVECODE_PROCESSOR); + sb.append('='); + sb.append(procs[i]); + } + + String[] oses = getOSNames(); + for (int i = 0; i < oses.length; i++) { + sb.append("; "); //$NON-NLS-1$ + sb.append(Constants.BUNDLE_NATIVECODE_OSNAME); + sb.append('='); + sb.append(oses[i]); + } + + VersionRange[] osRanges = getOSVersions(); + for (int i = 0; i < osRanges.length; i++) { + sb.append("; "); //$NON-NLS-1$ + sb.append(Constants.BUNDLE_NATIVECODE_OSVERSION); + sb.append("=\""); //$NON-NLS-1$ + sb.append(osRanges[i].toString()); + sb.append('"'); + } + + String[] langs = getLanguages(); + for (int i = 0; i < langs.length; i++) { + sb.append("; "); //$NON-NLS-1$ + sb.append(Constants.BUNDLE_NATIVECODE_LANGUAGE); + sb.append('='); + sb.append(langs[i]); + } + + Filter f = getFilter(); + if (f != null) { + sb.append("; "); //$NON-NLS-1$ + sb.append(Constants.SELECTION_FILTER_ATTRIBUTE); + sb.append("=\""); //$NON-NLS-1$ + sb.append(f.toString()); + sb.append('"'); + } + return (sb.toString()); + } + + void setInvalidNativePaths(boolean invalidNativePaths) { + this.invalidNativePaths = invalidNativePaths; + } + + void setOSNames(String[] osNames) { + synchronized (this.monitor) { + this.osNames = osNames; + } + } + + void setOSVersions(VersionRange[] osVersions) { + synchronized (this.monitor) { + this.osVersions = osVersions; + } + } + + void setFilter(String filter) throws InvalidSyntaxException { + this.filter = filter == null ? null : FilterImpl.newInstance(filter); + } + + void setLanguages(String[] languages) { + synchronized (this.monitor) { + this.languages = languages; + } + } + + void setNativePaths(String[] nativePaths) { + synchronized (this.monitor) { + this.nativePaths = nativePaths; + } + } + + void setProcessors(String[] processors) { + synchronized (this.monitor) { + this.processors = processors; + } + } + + void setSupplier(BundleDescription supplier) { + this.supplier = supplier; + } + + @SuppressWarnings("unchecked") + public Map<String, String> getDeclaredDirectives() { + return Collections.EMPTY_MAP; + } + + @SuppressWarnings("unchecked") + public Map<String, Object> getDeclaredAttributes() { + return Collections.EMPTY_MAP; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/NativeCodeSpecificationImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/NativeCodeSpecificationImpl.java new file mode 100644 index 000000000..883346331 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/NativeCodeSpecificationImpl.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2007, 2011 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import org.eclipse.osgi.internal.framework.AliasMapper; + +import java.util.*; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.Constants; +import org.osgi.framework.Filter; +import org.osgi.framework.Version; + +public class NativeCodeSpecificationImpl extends VersionConstraintImpl implements NativeCodeSpecification { + private static final NativeCodeDescription[] EMPTY_NATIVECODEDESCRIPTIONS = new NativeCodeDescription[0]; + private static AliasMapper aliasMapper = new AliasMapper(); + private NativeCodeDescription[] possibleSuppliers; + private boolean optional; + + public NativeCodeDescription[] getPossibleSuppliers() { + synchronized (this.monitor) { + if (possibleSuppliers == null) + return EMPTY_NATIVECODEDESCRIPTIONS; + return possibleSuppliers; + } + } + + void setPossibleSuppliers(NativeCodeDescription[] possibleSuppliers) { + synchronized (this.monitor) { + this.possibleSuppliers = possibleSuppliers; + } + } + + public boolean isOptional() { + synchronized (this.monitor) { + return optional; + } + } + + void setOptional(boolean optional) { + synchronized (this.monitor) { + this.optional = optional; + } + } + + public boolean isSatisfiedBy(BaseDescription supplier) { + if (!(supplier instanceof NativeCodeDescription)) + return false; + State containingState = getBundle().getContainingState(); + if (containingState == null) + return false; + Dictionary<Object, Object>[] platformProps = containingState.getPlatformProperties(); + NativeCodeDescription nativeSupplier = (NativeCodeDescription) supplier; + Filter filter = nativeSupplier.getFilter(); + boolean match = false; + for (int i = 0; i < platformProps.length && !match; i++) { + @SuppressWarnings("rawtypes") + Dictionary props = platformProps[i]; + if (filter != null && !filter.matchCase(props)) + continue; + String[] osNames = nativeSupplier.getOSNames(); + if (osNames.length == 0) + match = true; + else { + Object platformOS = platformProps[i].get(Constants.FRAMEWORK_OS_NAME); + Object aliasedPlatformOS = platformOS == null || !(platformOS instanceof String) ? platformOS : aliasMapper.aliasOSName((String) platformOS); + Object[] platformOSes; + if (aliasedPlatformOS instanceof Collection) + platformOSes = ((Collection<?>) aliasedPlatformOS).toArray(); + else + platformOSes = aliasedPlatformOS == null ? new Object[0] : new Object[] {aliasedPlatformOS}; + for (int j = 0; j < osNames.length && !match; j++) { + Object aliasedName = aliasMapper.aliasOSName(osNames[j]); + for (int k = 0; k < platformOSes.length; k++) { + if (aliasedName instanceof String) { + if (platformOSes[k].equals(aliasedName)) + match = true; + } else { + for (Iterator<?> iAliases = ((Collection<?>) aliasedName).iterator(); iAliases.hasNext() && !match;) + if (platformOSes[k].equals(iAliases.next())) + match = true; + } + } + } + } + if (!match) + continue; + match = false; + + String[] processors = nativeSupplier.getProcessors(); + if (processors.length == 0) + match = true; + else { + Object platformProcessor = platformProps[i].get(Constants.FRAMEWORK_PROCESSOR); + Object aliasedPlatformProcessor = platformProcessor == null || !(platformProcessor instanceof String) ? platformProcessor : aliasMapper.aliasProcessor((String) platformProcessor); + if (aliasedPlatformProcessor != null) + for (int j = 0; j < processors.length && !match; j++) { + String aliasedProcessor = aliasMapper.aliasProcessor(processors[j]); + if (aliasedPlatformProcessor.equals(aliasedProcessor)) + match = true; + } + } + if (!match) + return false; + match = false; + + String[] languages = nativeSupplier.getLanguages(); + if (languages.length == 0) + match = true; + else { + Object platformLanguage = platformProps[i].get(Constants.FRAMEWORK_LANGUAGE); + if (platformLanguage != null) + for (int j = 0; j < languages.length && !match; j++) { + if ((platformLanguage instanceof String) ? ((String) platformLanguage).equalsIgnoreCase(languages[j]) : platformLanguage.equals(languages[j])) + match = true; + } + } + if (!match) + return false; + match = false; + + VersionRange[] osVersions = nativeSupplier.getOSVersions(); + if (osVersions.length == 0 || platformProps[i].get(Constants.FRAMEWORK_OS_VERSION) == null) + match = true; + else { + Version osversion; + try { + osversion = Version.parseVersion((String) platformProps[i].get(Constants.FRAMEWORK_OS_VERSION)); + } catch (Exception e) { + osversion = Version.emptyVersion; + } + for (int j = 0; j < osVersions.length && !match; j++) { + if (osVersions[j].isIncluded(osversion)) + match = true; + } + } + } + return match; + } + + @Override + protected boolean hasMandatoryAttributes(String[] mandatory) { + return true; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + NativeCodeDescription[] suppliers = getPossibleSuppliers(); + for (int i = 0; i < suppliers.length; i++) { + if (i > 0) + sb.append(", "); //$NON-NLS-1$ + sb.append(suppliers[i].toString()); + } + + return sb.toString(); + } + + @SuppressWarnings("unchecked") + protected Map<String, String> getInternalDirectives() { + return Collections.EMPTY_MAP; + } + + @SuppressWarnings("unchecked") + protected Map<String, Object> getInteralAttributes() { + return Collections.EMPTY_MAP; + } + + @Override + protected String getInternalNameSpace() { + return null; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/ReadOnlyState.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/ReadOnlyState.java new file mode 100644 index 000000000..d0bc359ae --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/ReadOnlyState.java @@ -0,0 +1,236 @@ +/******************************************************************************* + * Copyright (c) 2004, 2011 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 + * Danail Nachev - ProSyst - bug 218625 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import java.util.*; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.BundleException; +import org.osgi.framework.Version; +import org.osgi.framework.hooks.resolver.ResolverHookFactory; + +public final class ReadOnlyState implements State { + private final State target; + + public ReadOnlyState(State target) { + this.target = target; + } + + public boolean addBundle(BundleDescription description) { + throw new UnsupportedOperationException(); + } + + public StateDelta compare(State state) throws BundleException { + return target.compare(state); + } + + public BundleDescription getBundle(long id) { + return target.getBundle(id); + } + + public BundleDescription getBundle(String symbolicName, Version version) { + return target.getBundle(symbolicName, version); + } + + public BundleDescription getBundleByLocation(String location) { + return target.getBundleByLocation(location); + } + + public BundleDescription[] getBundles() { + return target.getBundles(); + } + + public BundleDescription[] getBundles(String symbolicName) { + return target.getBundles(symbolicName); + } + + public StateDelta getChanges() { + return target.getChanges(); + } + + public ExportPackageDescription[] getExportedPackages() { + return target.getExportedPackages(); + } + + public StateObjectFactory getFactory() { + return target.getFactory(); + } + + public BundleDescription[] getResolvedBundles() { + return target.getResolvedBundles(); + } + + public long getTimeStamp() { + return target.getTimeStamp(); + } + + public boolean isEmpty() { + return target.isEmpty(); + } + + public boolean isResolved() { + return target.isResolved(); + } + + public boolean removeBundle(BundleDescription bundle) { + throw new UnsupportedOperationException(); + } + + public BundleDescription removeBundle(long bundleId) { + throw new UnsupportedOperationException(); + } + + public StateDelta resolve() { + throw new UnsupportedOperationException(); + } + + public StateDelta resolve(boolean incremental) { + throw new UnsupportedOperationException(); + } + + public StateDelta resolve(BundleDescription[] discard) { + throw new UnsupportedOperationException(); + } + + public StateDelta resolve(BundleDescription[] resolve, boolean discard) { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("deprecation") + public void setOverrides(Object value) { + throw new UnsupportedOperationException(); + } + + public boolean updateBundle(BundleDescription newDescription) { + throw new UnsupportedOperationException(); + } + + public void resolveConstraint(VersionConstraint constraint, BaseDescription supplier) { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated + */ + public void resolveBundle(BundleDescription bundle, boolean status, BundleDescription[] hosts, ExportPackageDescription[] selectedExports, BundleDescription[] resolvedRequires, ExportPackageDescription[] resolvedImports) { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated + */ + public void resolveBundle(BundleDescription bundle, boolean status, BundleDescription[] host, ExportPackageDescription[] selectedExports, ExportPackageDescription[] substitutedExports, BundleDescription[] resolvedRequires, ExportPackageDescription[] resolveImports) { + throw new UnsupportedOperationException(); + } + + public void resolveBundle(BundleDescription bundle, boolean status, BundleDescription[] hosts, ExportPackageDescription[] selectedExports, ExportPackageDescription[] substitutedExports, GenericDescription[] selectedCapabilities, BundleDescription[] resolvedRequires, ExportPackageDescription[] resolvedImports, GenericDescription[] resolvedCapabilities, Map<String, List<StateWire>> resolvedRequirements) { + throw new UnsupportedOperationException(); + } + + public void removeBundleComplete(BundleDescription bundle) { + throw new UnsupportedOperationException(); + } + + public Resolver getResolver() { + return null; + } + + public void setResolver(Resolver value) { + throw new UnsupportedOperationException(); + } + + public boolean setPlatformProperties(Dictionary<?, ?> platformProperties) { + throw new UnsupportedOperationException(); + } + + public boolean setPlatformProperties(Dictionary<?, ?> platformProperties[]) { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("rawtypes") + public Dictionary[] getPlatformProperties() { + return target.getPlatformProperties(); + } + + public ExportPackageDescription linkDynamicImport(BundleDescription importingBundle, String requestedPackage) { + throw new UnsupportedOperationException(); + } + + public void setTimeStamp(long timeStamp) { + throw new UnsupportedOperationException(); + } + + public ExportPackageDescription[] getSystemPackages() { + return target.getSystemPackages(); + } + + public void addResolverError(BundleDescription bundle, int type, String data, VersionConstraint unsatisfied) { + throw new UnsupportedOperationException(); + } + + public ResolverError[] getResolverErrors(BundleDescription bundle) { + return target.getResolverErrors(bundle); + } + + public void removeResolverErrors(BundleDescription bundle) { + throw new UnsupportedOperationException(); + } + + public StateHelper getStateHelper() { + return StateHelperImpl.getInstance(); + } + + public long getHighestBundleId() { + return target.getHighestBundleId(); + } + + public void setNativePathsInvalid(NativeCodeDescription nativeCodeDescription, boolean hasInvalidPaths) { + throw new UnsupportedOperationException(); + } + + public BundleDescription[] getDisabledBundles() { + return target.getDisabledBundles(); + } + + public void addDisabledInfo(DisabledInfo disabledInfo) { + throw new UnsupportedOperationException(); + } + + public DisabledInfo[] getDisabledInfos(BundleDescription bundle) { + return target.getDisabledInfos(bundle); + } + + public DisabledInfo getDisabledInfo(BundleDescription bundle, String policyName) { + return target.getDisabledInfo(bundle, policyName); + } + + public void removeDisabledInfo(DisabledInfo disabledInfo) { + throw new UnsupportedOperationException(); + } + + public BundleDescription[] getRemovalPending() { + throw new UnsupportedOperationException(); + } + + public Collection<BundleDescription> getDependencyClosure(Collection<BundleDescription> bundles) { + return target.getDependencyClosure(bundles); + } + + public void addDynamicImportPackages(BundleDescription importingBundle, ImportPackageSpecification[] dynamicImports) { + throw new UnsupportedOperationException(); + } + + public void setResolverHookFactory(ResolverHookFactory hookFactory) { + // do nothing + } + +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/ResolverErrorImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/ResolverErrorImpl.java new file mode 100644 index 000000000..e9131afb3 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/ResolverErrorImpl.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2006, 2010 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import org.eclipse.osgi.service.resolver.*; +import org.eclipse.osgi.util.NLS; + +public final class ResolverErrorImpl implements ResolverError { + private final BundleDescriptionImpl bundle; + private final int type; + private final String data; + private final VersionConstraint unsatisfied; + + public ResolverErrorImpl(BundleDescriptionImpl bundle, int type, String data, VersionConstraint unsatisfied) { + this.bundle = bundle; + this.data = data; + this.type = type; + this.unsatisfied = unsatisfied; + } + + public BundleDescription getBundle() { + return bundle; + } + + public int getType() { + return type; + } + + public String getData() { + return data; + } + + public VersionConstraint getUnsatisfiedConstraint() { + return unsatisfied; + } + + public String toString() { + switch (getType()) { + case ResolverError.EXPORT_PACKAGE_PERMISSION : + case ResolverError.IMPORT_PACKAGE_PERMISSION : + case ResolverError.REQUIRE_BUNDLE_PERMISSION : + case ResolverError.PROVIDE_BUNDLE_PERMISSION : + case ResolverError.FRAGMENT_BUNDLE_PERMISSION : + case ResolverError.HOST_BUNDLE_PERMISSION : + case ResolverError.REQUIRE_CAPABILITY_PERMISSION : + case ResolverError.PROVIDE_CAPABILITY_PERMISSION : + return NLS.bind(StateMsg.RES_ERROR_MISSING_PERMISSION, getData()); + case ResolverError.MISSING_IMPORT_PACKAGE : + case ResolverError.MISSING_REQUIRE_BUNDLE : + case ResolverError.MISSING_FRAGMENT_HOST : + case ResolverError.MISSING_EXECUTION_ENVIRONMENT : + case ResolverError.MISSING_GENERIC_CAPABILITY : + return NLS.bind(StateMsg.RES_ERROR_MISSING_CONSTRAINT, getData()); + case ResolverError.FRAGMENT_CONFLICT : + return NLS.bind(StateMsg.RES_ERROR_FRAGMENT_CONFLICT, getData()); + case ResolverError.IMPORT_PACKAGE_USES_CONFLICT : + case ResolverError.REQUIRE_BUNDLE_USES_CONFLICT : + return NLS.bind(StateMsg.RES_ERROR_USES_CONFLICT, getData()); + case ResolverError.SINGLETON_SELECTION : + return NLS.bind(StateMsg.RES_ERROR_SINGLETON_CONFLICT, getData()); + case ResolverError.PLATFORM_FILTER : + return NLS.bind(StateMsg.RES_ERROR_PLATFORM_FILTER, getData()); + case ResolverError.NO_NATIVECODE_MATCH : + return NLS.bind(StateMsg.RES_ERROR_NO_NATIVECODE_MATCH, getData()); + case ResolverError.INVALID_NATIVECODE_PATHS : + return NLS.bind(StateMsg.RES_ERROR_NATIVECODE_PATH_INVALID, getData()); + case ResolverError.DISABLED_BUNDLE : + return NLS.bind(StateMsg.RES_ERROR_DISABLEDBUNDLE, getData()); + default : + return StateMsg.RES_ERROR_UNKNOWN; + } + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateBuilder.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateBuilder.java new file mode 100644 index 000000000..332eedb48 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateBuilder.java @@ -0,0 +1,913 @@ +/******************************************************************************* + * Copyright (c) 2003, 2012 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 + * Danail Nachev - ProSyst - bug 218625 + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import org.eclipse.osgi.internal.framework.EquinoxContainer; +import org.eclipse.osgi.internal.framework.FilterImpl; + +import java.lang.reflect.Constructor; +import java.util.*; +import org.eclipse.osgi.framework.internal.core.*; +import org.eclipse.osgi.service.resolver.*; +import org.eclipse.osgi.service.resolver.VersionRange; +import org.eclipse.osgi.util.ManifestElement; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; +import org.osgi.framework.namespace.BundleNamespace; +import org.osgi.framework.namespace.IdentityNamespace; +import org.osgi.resource.Namespace; + +/** + * This class builds bundle description objects from manifests + */ +public class StateBuilder { + private static final String[] DEFINED_EXPORT_PACKAGE_DIRECTIVES = {Constants.USES_DIRECTIVE, Constants.INCLUDE_DIRECTIVE, Constants.EXCLUDE_DIRECTIVE, StateImpl.FRIENDS_DIRECTIVE, StateImpl.INTERNAL_DIRECTIVE, Constants.MANDATORY_DIRECTIVE}; + private static final String[] DEFINED_IMPORT_PACKAGE_DIRECTIVES = {Constants.RESOLUTION_DIRECTIVE}; + private static final String[] DEFINED_PACKAGE_MATCHING_ATTRS = {Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, Constants.BUNDLE_VERSION_ATTRIBUTE, Constants.PACKAGE_SPECIFICATION_VERSION, Constants.VERSION_ATTRIBUTE}; + private static final String[] DEFINED_REQUIRE_BUNDLE_DIRECTIVES = {Constants.RESOLUTION_DIRECTIVE, Constants.VISIBILITY_DIRECTIVE}; + private static final String[] DEFINED_FRAGMENT_HOST_DIRECTIVES = {Constants.EXTENSION_DIRECTIVE}; + static final String[] DEFINED_BSN_DIRECTIVES = {Constants.SINGLETON_DIRECTIVE, Constants.FRAGMENT_ATTACHMENT_DIRECTIVE, Constants.MANDATORY_DIRECTIVE}; + static final String[] DEFINED_BSN_MATCHING_ATTRS = {Constants.BUNDLE_VERSION_ATTRIBUTE, StateImpl.OPTIONAL_ATTRIBUTE, StateImpl.REPROVIDE_ATTRIBUTE}; + private static final String[] DEFINED_REQUIRE_CAPABILITY_DIRECTIVES = {Constants.RESOLUTION_DIRECTIVE, Constants.FILTER_DIRECTIVE, Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE}; + private static final String[] DEFINED_REQUIRE_CAPABILITY_ATTRS = {}; + private static final String[] DEFINED_OSGI_VALIDATE_HEADERS = {Constants.IMPORT_PACKAGE, Constants.DYNAMICIMPORT_PACKAGE, Constants.EXPORT_PACKAGE, Constants.FRAGMENT_HOST, Constants.BUNDLE_SYMBOLICNAME, Constants.REQUIRE_BUNDLE}; + static final String GENERIC_REQUIRE = "Eclipse-GenericRequire"; //$NON-NLS-1$ + static final String GENERIC_CAPABILITY = "Eclipse-GenericCapability"; //$NON-NLS-1$ + + private static final String ATTR_TYPE_STRING = "string"; //$NON-NLS-1$ + private static final String ATTR_TYPE_VERSION = "version"; //$NON-NLS-1$ + private static final String ATTR_TYPE_URI = "uri"; //$NON-NLS-1$ + private static final String ATTR_TYPE_LONG = "long"; //$NON-NLS-1$ + private static final String ATTR_TYPE_DOUBLE = "double"; //$NON-NLS-1$ + private static final String ATTR_TYPE_SET = "set"; //$NON-NLS-1$ + private static final String ATTR_TYPE_LIST = "List"; //$NON-NLS-1$ + private static final String OPTIONAL_ATTR = "optional"; //$NON-NLS-1$ + private static final String MULTIPLE_ATTR = "multiple"; //$NON-NLS-1$ + private static final String TRUE = "true"; //$NON-NLS-1$ + + static BundleDescription createBundleDescription(StateImpl state, Dictionary<String, String> manifest, String location) throws BundleException { + BundleDescriptionImpl result = new BundleDescriptionImpl(); + String manifestVersionHeader = manifest.get(Constants.BUNDLE_MANIFESTVERSION); + boolean jreBundle = "true".equals(manifest.get(StateImpl.Eclipse_JREBUNDLE)); //$NON-NLS-1$ + int manifestVersion = 1; + if (manifestVersionHeader != null) + manifestVersion = Integer.parseInt(manifestVersionHeader); + if (manifestVersion >= 2) + validateHeaders(manifest, jreBundle); + + // retrieve the symbolic-name and the singleton status + String symbolicNameHeader = manifest.get(Constants.BUNDLE_SYMBOLICNAME); + if (symbolicNameHeader != null) { + ManifestElement[] symbolicNameElements = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, symbolicNameHeader); + if (symbolicNameElements.length > 0) { + ManifestElement bsnElement = symbolicNameElements[0]; + result.setSymbolicName(bsnElement.getValue()); + String singleton = bsnElement.getDirective(Constants.SINGLETON_DIRECTIVE); + if (singleton == null) // TODO this is for backward compatibility; need to check manifest version < 2 to allow this after everyone has converted to new syntax + singleton = bsnElement.getAttribute(Constants.SINGLETON_DIRECTIVE); + result.setStateBit(BundleDescriptionImpl.SINGLETON, "true".equals(singleton)); //$NON-NLS-1$ + String fragmentAttachment = bsnElement.getDirective(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE); + if (fragmentAttachment != null) { + if (fragmentAttachment.equals(Constants.FRAGMENT_ATTACHMENT_RESOLVETIME)) { + result.setStateBit(BundleDescriptionImpl.ATTACH_FRAGMENTS, true); + result.setStateBit(BundleDescriptionImpl.DYNAMIC_FRAGMENTS, false); + } else if (fragmentAttachment.equals(Constants.FRAGMENT_ATTACHMENT_NEVER)) { + result.setStateBit(BundleDescriptionImpl.ATTACH_FRAGMENTS, false); + result.setStateBit(BundleDescriptionImpl.DYNAMIC_FRAGMENTS, false); + } + } + result.setDirective(Constants.MANDATORY_DIRECTIVE, ManifestElement.getArrayFromList(bsnElement.getDirective(Constants.MANDATORY_DIRECTIVE))); + result.setAttributes(getAttributes(bsnElement, DEFINED_BSN_MATCHING_ATTRS)); + result.setArbitraryDirectives(getDirectives(bsnElement, DEFINED_BSN_DIRECTIVES)); + } + } + // retrieve other headers + String version = manifest.get(Constants.BUNDLE_VERSION); + try { + result.setVersion((version != null) ? Version.parseVersion(version) : Version.emptyVersion); + } catch (IllegalArgumentException ex) { + if (manifestVersion >= 2) { + String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, Constants.BUNDLE_VERSION, version); + throw new BundleException(message + " : " + ex.getMessage(), BundleException.MANIFEST_ERROR, ex); //$NON-NLS-1$ + } + // prior to R4 the Bundle-Version header was not interpreted by the Framework; + // must not fail for old R3 style bundles + } + result.setLocation(location); + result.setPlatformFilter(manifest.get(StateImpl.ECLIPSE_PLATFORMFILTER)); + String[] brees = ManifestElement.getArrayFromList(manifest.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT)); + result.setExecutionEnvironments(brees); + ManifestElement[] host = ManifestElement.parseHeader(Constants.FRAGMENT_HOST, manifest.get(Constants.FRAGMENT_HOST)); + if (host != null) + result.setHost(createHostSpecification(host[0], state)); + ManifestElement[] exports = ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, manifest.get(Constants.EXPORT_PACKAGE)); + ManifestElement[] provides = ManifestElement.parseHeader(StateImpl.PROVIDE_PACKAGE, manifest.get(StateImpl.PROVIDE_PACKAGE)); + boolean strict = state != null && state.inStrictMode(); + List<String> providedExports = new ArrayList<String>(provides == null ? 0 : provides.length); + result.setExportPackages(createExportPackages(exports, provides, providedExports, strict)); + ManifestElement[] imports = ManifestElement.parseHeader(Constants.IMPORT_PACKAGE, manifest.get(Constants.IMPORT_PACKAGE)); + ManifestElement[] dynamicImports = ManifestElement.parseHeader(Constants.DYNAMICIMPORT_PACKAGE, manifest.get(Constants.DYNAMICIMPORT_PACKAGE)); + result.setImportPackages(createImportPackages(result.getExportPackages(), providedExports, imports, dynamicImports, manifestVersion)); + ManifestElement[] requires = ManifestElement.parseHeader(Constants.REQUIRE_BUNDLE, manifest.get(Constants.REQUIRE_BUNDLE)); + result.setRequiredBundles(createRequiredBundles(requires)); + String[][] genericAliases = getGenericAliases(state); + ManifestElement[] genericRequires = getGenericRequires(manifest, genericAliases); + ManifestElement[] osgiRequires = ManifestElement.parseHeader(Constants.REQUIRE_CAPABILITY, manifest.get(Constants.REQUIRE_CAPABILITY)); + result.setGenericRequires(createGenericRequires(genericRequires, osgiRequires, brees)); + ManifestElement[] genericCapabilities = getGenericCapabilities(manifest, genericAliases); + ManifestElement[] osgiCapabilities = ManifestElement.parseHeader(Constants.PROVIDE_CAPABILITY, manifest.get(Constants.PROVIDE_CAPABILITY)); + result.setGenericCapabilities(createGenericCapabilities(genericCapabilities, osgiCapabilities, result)); + ManifestElement[] nativeCode = ManifestElement.parseHeader(Constants.BUNDLE_NATIVECODE, manifest.get(Constants.BUNDLE_NATIVECODE)); + result.setNativeCodeSpecification(createNativeCode(nativeCode)); + return result; + } + + private static ManifestElement[] getGenericRequires(Dictionary<String, String> manifest, String[][] genericAliases) throws BundleException { + ManifestElement[] genericRequires = ManifestElement.parseHeader(GENERIC_REQUIRE, manifest.get(GENERIC_REQUIRE)); + List<ManifestElement> aliasList = null; + if (genericAliases.length > 0) { + aliasList = new ArrayList<ManifestElement>(genericRequires == null ? 0 : genericRequires.length); + for (int i = 0; i < genericAliases.length; i++) { + ManifestElement[] aliasReqs = ManifestElement.parseHeader(genericAliases[i][1], manifest.get(genericAliases[i][1])); + if (aliasReqs == null) + continue; + for (int j = 0; j < aliasReqs.length; j++) { + StringBuffer strBuf = new StringBuffer(); + strBuf.append(aliasReqs[j].getValue()).append(':').append(genericAliases[i][2]); + String filter = aliasReqs[j].getAttribute(Constants.SELECTION_FILTER_ATTRIBUTE); + if (filter != null) + strBuf.append("; ").append(Constants.SELECTION_FILTER_ATTRIBUTE).append(filter).append("=\"").append(filter).append("\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + ManifestElement[] withType = ManifestElement.parseHeader(genericAliases[i][1], strBuf.toString()); + aliasList.add(withType[0]); + } + } + } + if (aliasList == null || aliasList.size() == 0) + return genericRequires; + if (genericRequires != null) + for (int i = 0; i < genericRequires.length; i++) + aliasList.add(genericRequires[i]); + return aliasList.toArray(new ManifestElement[aliasList.size()]); + } + + private static ManifestElement[] getGenericCapabilities(Dictionary<String, String> manifest, String[][] genericAliases) throws BundleException { + ManifestElement[] genericCapabilities = ManifestElement.parseHeader(GENERIC_CAPABILITY, manifest.get(GENERIC_CAPABILITY)); + List<ManifestElement> aliasList = null; + if (genericAliases.length > 0) { + aliasList = new ArrayList<ManifestElement>(genericCapabilities == null ? 0 : genericCapabilities.length); + for (int i = 0; i < genericAliases.length; i++) { + ManifestElement[] aliasCapabilities = ManifestElement.parseHeader(genericAliases[i][0], manifest.get(genericAliases[i][0])); + if (aliasCapabilities == null) + continue; + for (int j = 0; j < aliasCapabilities.length; j++) { + StringBuffer strBuf = new StringBuffer(); + strBuf.append(aliasCapabilities[j].getValue()).append(':').append(genericAliases[i][2]); + for (Enumeration<String> keys = aliasCapabilities[j].getKeys(); keys != null && keys.hasMoreElements();) { + String key = keys.nextElement(); + strBuf.append("; ").append(key).append("=\"").append(aliasCapabilities[j].getAttribute(key)).append("\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + ManifestElement[] withTypes = ManifestElement.parseHeader(genericAliases[i][0], strBuf.toString()); + aliasList.add(withTypes[0]); + } + } + } + if (aliasList == null || aliasList.size() == 0) + return genericCapabilities; + if (genericCapabilities != null) + for (int i = 0; i < genericCapabilities.length; i++) + aliasList.add(genericCapabilities[i]); + return aliasList.toArray(new ManifestElement[aliasList.size()]); + } + + private static String[][] getGenericAliases(StateImpl state) { + String genericAliasesProp = getPlatformProperty(state, "osgi.genericAliases"); //$NON-NLS-1$ + if (genericAliasesProp == null) + return new String[0][0]; + String[] aliases = ManifestElement.getArrayFromList(genericAliasesProp, ","); //$NON-NLS-1$ + String[][] result = new String[aliases.length][]; + for (int i = 0; i < aliases.length; i++) + result[i] = ManifestElement.getArrayFromList(aliases[i], ":"); //$NON-NLS-1$ + return result; + } + + private static String getPlatformProperty(State state, String key) { + Dictionary<Object, Object>[] platformProps = state == null ? null : state.getPlatformProperties(); + return platformProps == null || platformProps.length == 0 ? null : (String) platformProps[0].get(key); + } + + private static void validateHeaders(Dictionary<String, String> manifest, boolean jreBundle) throws BundleException { + for (int i = 0; i < DEFINED_OSGI_VALIDATE_HEADERS.length; i++) { + String header = manifest.get(DEFINED_OSGI_VALIDATE_HEADERS[i]); + if (header != null) { + ManifestElement[] elements = ManifestElement.parseHeader(DEFINED_OSGI_VALIDATE_HEADERS[i], header); + checkForDuplicateDirectivesAttributes(DEFINED_OSGI_VALIDATE_HEADERS[i], elements); + if (DEFINED_OSGI_VALIDATE_HEADERS[i] == Constants.IMPORT_PACKAGE) + checkImportExportSyntax(DEFINED_OSGI_VALIDATE_HEADERS[i], elements, false, false, jreBundle); + if (DEFINED_OSGI_VALIDATE_HEADERS[i] == Constants.DYNAMICIMPORT_PACKAGE) + checkImportExportSyntax(DEFINED_OSGI_VALIDATE_HEADERS[i], elements, false, true, jreBundle); + if (DEFINED_OSGI_VALIDATE_HEADERS[i] == Constants.EXPORT_PACKAGE) + checkImportExportSyntax(DEFINED_OSGI_VALIDATE_HEADERS[i], elements, true, false, jreBundle); + if (DEFINED_OSGI_VALIDATE_HEADERS[i] == Constants.FRAGMENT_HOST) + checkExtensionBundle(DEFINED_OSGI_VALIDATE_HEADERS[i], elements); + } else if (DEFINED_OSGI_VALIDATE_HEADERS[i] == Constants.BUNDLE_SYMBOLICNAME) { + throw new BundleException(NLS.bind(StateMsg.HEADER_REQUIRED, Constants.BUNDLE_SYMBOLICNAME), BundleException.MANIFEST_ERROR); + } + } + } + + private static BundleSpecification[] createRequiredBundles(ManifestElement[] specs) { + if (specs == null) + return null; + BundleSpecification[] result = new BundleSpecification[specs.length]; + for (int i = 0; i < specs.length; i++) + result[i] = createRequiredBundle(specs[i]); + return result; + } + + static BundleSpecification createRequiredBundle(ManifestElement spec) { + BundleSpecificationImpl result = new BundleSpecificationImpl(); + result.setName(spec.getValue()); + result.setVersionRange(getVersionRange(spec.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE))); + result.setExported(Constants.VISIBILITY_REEXPORT.equals(spec.getDirective(Constants.VISIBILITY_DIRECTIVE)) || "true".equals(spec.getAttribute(StateImpl.REPROVIDE_ATTRIBUTE))); //$NON-NLS-1$ + result.setOptional(Constants.RESOLUTION_OPTIONAL.equals(spec.getDirective(Constants.RESOLUTION_DIRECTIVE)) || "true".equals(spec.getAttribute(StateImpl.OPTIONAL_ATTRIBUTE))); //$NON-NLS-1$ + result.setAttributes(getAttributes(spec, DEFINED_BSN_MATCHING_ATTRS)); + result.setArbitraryDirectives(getDirectives(spec, DEFINED_REQUIRE_BUNDLE_DIRECTIVES)); + return result; + } + + private static ImportPackageSpecification[] createImportPackages(ExportPackageDescription[] exported, List<String> providedExports, ManifestElement[] imported, ManifestElement[] dynamicImported, int manifestVersion) { + List<ImportPackageSpecification> allImports = null; + if (manifestVersion < 2) { + // add implicit imports for each exported package if manifest verions is less than 2. + if (exported.length == 0 && imported == null && dynamicImported == null) + return null; + allImports = new ArrayList<ImportPackageSpecification>(exported.length + (imported == null ? 0 : imported.length)); + for (int i = 0; i < exported.length; i++) { + if (providedExports.contains(exported[i].getName())) + continue; + ImportPackageSpecificationImpl result = new ImportPackageSpecificationImpl(); + result.setName(exported[i].getName()); + result.setVersionRange(getVersionRange(exported[i].getVersion().toString())); + result.setDirective(Constants.RESOLUTION_DIRECTIVE, ImportPackageSpecification.RESOLUTION_STATIC); + allImports.add(result); + } + } else { + allImports = new ArrayList<ImportPackageSpecification>(imported == null ? 0 : imported.length); + } + + // add dynamics first so they will get overriden by static imports if + // the same package is dyanamically imported and statically imported. + if (dynamicImported != null) + for (int i = 0; i < dynamicImported.length; i++) + addImportPackages(dynamicImported[i], allImports, manifestVersion, true); + if (imported != null) + for (int i = 0; i < imported.length; i++) + addImportPackages(imported[i], allImports, manifestVersion, false); + return allImports.toArray(new ImportPackageSpecification[allImports.size()]); + } + + public static void addImportPackages(ManifestElement importPackage, List<ImportPackageSpecification> allImports, int manifestVersion, boolean dynamic) { + String[] importNames = importPackage.getValueComponents(); + for (int i = 0; i < importNames.length; i++) { + // do not allow for multiple imports of same package of manifest version < 2 + if (manifestVersion < 2) { + Iterator<ImportPackageSpecification> iter = allImports.iterator(); + while (iter.hasNext()) + if (importNames[i].equals(iter.next().getName())) + iter.remove(); + } + + ImportPackageSpecificationImpl result = new ImportPackageSpecificationImpl(); + result.setName(importNames[i]); + // set common attributes for both dynamic and static imports + String versionString = importPackage.getAttribute(Constants.VERSION_ATTRIBUTE); + if (versionString == null) // specification-version aliases to version + versionString = importPackage.getAttribute(Constants.PACKAGE_SPECIFICATION_VERSION); + result.setVersionRange(getVersionRange(versionString)); + result.setBundleSymbolicName(importPackage.getAttribute(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE)); + result.setBundleVersionRange(getVersionRange(importPackage.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE))); + // only set the matching attributes if manifest version >= 2 + if (manifestVersion >= 2) + result.setAttributes(getAttributes(importPackage, DEFINED_PACKAGE_MATCHING_ATTRS)); + + if (dynamic) + result.setDirective(Constants.RESOLUTION_DIRECTIVE, ImportPackageSpecification.RESOLUTION_DYNAMIC); + else + result.setDirective(Constants.RESOLUTION_DIRECTIVE, getResolution(importPackage.getDirective(Constants.RESOLUTION_DIRECTIVE))); + result.setArbitraryDirectives(getDirectives(importPackage, DEFINED_IMPORT_PACKAGE_DIRECTIVES)); + + allImports.add(result); + } + } + + private static String getResolution(String resolution) { + String result = ImportPackageSpecification.RESOLUTION_STATIC; + if (Constants.RESOLUTION_OPTIONAL.equals(resolution) || ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(resolution)) + result = resolution; + return result; + } + + static ExportPackageDescription[] createExportPackages(ManifestElement[] exported, ManifestElement[] provides, List<String> providedExports, boolean strict) { + int numExports = (exported == null ? 0 : exported.length) + (provides == null ? 0 : provides.length); + if (numExports == 0) + return null; + List<ExportPackageDescription> allExports = new ArrayList<ExportPackageDescription>(numExports); + if (exported != null) + for (int i = 0; i < exported.length; i++) + addExportPackages(exported[i], allExports, strict); + if (provides != null) + addProvidePackages(provides, allExports, providedExports); + return allExports.toArray(new ExportPackageDescription[allExports.size()]); + } + + static void addExportPackages(ManifestElement exportPackage, List<ExportPackageDescription> allExports, boolean strict) { + String[] exportNames = exportPackage.getValueComponents(); + for (int i = 0; i < exportNames.length; i++) { + // if we are in strict mode and the package is marked as internal, skip it. + if (strict && "true".equals(exportPackage.getDirective(StateImpl.INTERNAL_DIRECTIVE))) //$NON-NLS-1$ + continue; + ExportPackageDescriptionImpl result = new ExportPackageDescriptionImpl(); + result.setName(exportNames[i]); + String versionString = exportPackage.getAttribute(Constants.VERSION_ATTRIBUTE); + if (versionString == null) // specification-version aliases to version + versionString = exportPackage.getAttribute(Constants.PACKAGE_SPECIFICATION_VERSION); + if (versionString != null) + result.setVersion(Version.parseVersion(versionString)); + result.setDirective(Constants.USES_DIRECTIVE, ManifestElement.getArrayFromList(exportPackage.getDirective(Constants.USES_DIRECTIVE))); + result.setDirective(Constants.INCLUDE_DIRECTIVE, exportPackage.getDirective(Constants.INCLUDE_DIRECTIVE)); + result.setDirective(Constants.EXCLUDE_DIRECTIVE, exportPackage.getDirective(Constants.EXCLUDE_DIRECTIVE)); + result.setDirective(StateImpl.FRIENDS_DIRECTIVE, ManifestElement.getArrayFromList(exportPackage.getDirective(StateImpl.FRIENDS_DIRECTIVE))); + result.setDirective(StateImpl.INTERNAL_DIRECTIVE, Boolean.valueOf(exportPackage.getDirective(StateImpl.INTERNAL_DIRECTIVE))); + result.setDirective(Constants.MANDATORY_DIRECTIVE, ManifestElement.getArrayFromList(exportPackage.getDirective(Constants.MANDATORY_DIRECTIVE))); + result.setAttributes(getAttributes(exportPackage, DEFINED_PACKAGE_MATCHING_ATTRS)); + result.setArbitraryDirectives(getDirectives(exportPackage, DEFINED_EXPORT_PACKAGE_DIRECTIVES)); + allExports.add(result); + } + } + + private static void addProvidePackages(ManifestElement[] provides, List<ExportPackageDescription> allExports, List<String> providedExports) { + ExportPackageDescription[] currentExports = allExports.toArray(new ExportPackageDescription[allExports.size()]); + for (int i = 0; i < provides.length; i++) { + boolean duplicate = false; + for (int j = 0; j < currentExports.length; j++) + if (provides[i].getValue().equals(currentExports[j].getName())) { + duplicate = true; + break; + } + if (!duplicate) { + ExportPackageDescriptionImpl result = new ExportPackageDescriptionImpl(); + result.setName(provides[i].getValue()); + allExports.add(result); + } + providedExports.add(provides[i].getValue()); + } + } + + static Map<String, String> getDirectives(ManifestElement element, String[] definedDirectives) { + Enumeration<String> keys = element.getDirectiveKeys(); + if (keys == null) + return null; + Map<String, String> arbitraryDirectives = null; + keyloop: while (keys.hasMoreElements()) { + String key = keys.nextElement(); + for (String definedDirective : definedDirectives) { + if (definedDirective.equals(key)) + continue keyloop; + } + if (arbitraryDirectives == null) + arbitraryDirectives = new HashMap<String, String>(); + arbitraryDirectives.put(key, element.getDirective(key)); + } + return arbitraryDirectives; + } + + static Map<String, Object> getAttributes(ManifestElement element, String[] definedAttrs) { + Enumeration<String> keys = element.getKeys(); + Map<String, Object> arbitraryAttrs = null; + if (keys == null) + return null; + while (keys.hasMoreElements()) { + boolean definedAttr = false; + String key = keys.nextElement(); + for (int i = 0; i < definedAttrs.length; i++) { + if (definedAttrs[i].equals(key)) { + definedAttr = true; + break; + } + } + String value = element.getAttribute(key); + int colonIndex = key.indexOf(':'); + String type = ATTR_TYPE_STRING; + if (colonIndex > 0) { + type = key.substring(colonIndex + 1).trim(); + key = key.substring(0, colonIndex).trim(); + } + if (!definedAttr) { + if (arbitraryAttrs == null) + arbitraryAttrs = new HashMap<String, Object>(); + arbitraryAttrs.put(key, convertValue(type, value)); + } + } + return arbitraryAttrs; + } + + private static Object convertValue(String type, String value) { + + if (ATTR_TYPE_STRING.equalsIgnoreCase(type)) + return value; + + String trimmed = value.trim(); + if (ATTR_TYPE_DOUBLE.equalsIgnoreCase(type)) + return new Double(trimmed); + else if (ATTR_TYPE_LONG.equalsIgnoreCase(type)) + return new Long(trimmed); + else if (ATTR_TYPE_URI.equalsIgnoreCase(type)) + try { + Class<?> uriClazz = Class.forName("java.net.URI"); //$NON-NLS-1$ + Constructor<?> constructor = uriClazz.getConstructor(new Class[] {String.class}); + return constructor.newInstance(new Object[] {trimmed}); + } catch (ClassNotFoundException e) { + // oh well cannot support; just use string + return value; + } catch (RuntimeException e) { // got some reflection exception + throw e; + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + else if (ATTR_TYPE_VERSION.equalsIgnoreCase(type)) + return new Version(trimmed); + else if (ATTR_TYPE_SET.equalsIgnoreCase(type)) + return ManifestElement.getArrayFromList(trimmed, ","); //$NON-NLS-1$ + + // assume list type, anything else will throw an exception + Tokenizer listTokenizer = new Tokenizer(type); + String listType = listTokenizer.getToken("<"); //$NON-NLS-1$ + if (!ATTR_TYPE_LIST.equalsIgnoreCase(listType)) + throw new RuntimeException("Unsupported type: " + type); //$NON-NLS-1$ + char c = listTokenizer.getChar(); + String componentType = ATTR_TYPE_STRING; + if (c == '<') { + componentType = listTokenizer.getToken(">"); //$NON-NLS-1$ + if (listTokenizer.getChar() != '>') + throw new RuntimeException("Invalid type, missing ending '>' : " + type); //$NON-NLS-1$ + } + List<String> tokens = new Tokenizer(value).getEscapedTokens(","); //$NON-NLS-1$ + List<Object> components = new ArrayList<Object>(); + for (String component : tokens) { + components.add(convertValue(componentType, component)); + } + return components; + } + + static HostSpecification createHostSpecification(ManifestElement spec, State state) { + if (spec == null) + return null; + HostSpecificationImpl result = new HostSpecificationImpl(); + result.setName(spec.getValue()); + result.setVersionRange(getVersionRange(spec.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE))); + String multiple = spec.getDirective("multiple-hosts"); //$NON-NLS-1$ + if (multiple == null) + multiple = getPlatformProperty(state, "osgi.support.multipleHosts"); //$NON-NLS-1$ + result.setIsMultiHost("true".equals(multiple)); //$NON-NLS-1$ + result.setAttributes(getAttributes(spec, DEFINED_BSN_MATCHING_ATTRS)); + result.setArbitraryDirectives(getDirectives(spec, DEFINED_FRAGMENT_HOST_DIRECTIVES)); + return result; + } + + private static GenericSpecification[] createGenericRequires(ManifestElement[] equinoxRequires, ManifestElement[] osgiRequires, String[] brees) throws BundleException { + List<GenericSpecification> result = createEquinoxRequires(equinoxRequires); + result = createOSGiRequires(osgiRequires, result); + result = convertBREEs(brees, result); + return result == null ? null : result.toArray(new GenericSpecification[result.size()]); + } + + static List<GenericSpecification> convertBREEs(String[] brees, List<GenericSpecification> result) throws BundleException { + if (brees == null || brees.length == 0) + return result; + if (result == null) + result = new ArrayList<GenericSpecification>(brees.length); + List<String> breeFilters = new ArrayList<String>(); + for (String bree : brees) + breeFilters.add(createOSGiEERequirementFilter(bree)); + String filterSpec; + if (breeFilters.size() == 1) { + filterSpec = breeFilters.get(0); + } else { + StringBuffer filterBuf = new StringBuffer("(|"); //$NON-NLS-1$ + for (String breeFilter : breeFilters) { + filterBuf.append(breeFilter); + } + filterSpec = filterBuf.append(")").toString(); //$NON-NLS-1$ + } + GenericSpecificationImpl spec = new GenericSpecificationImpl(); + spec.setResolution(GenericSpecificationImpl.RESOLUTION_FROM_BREE); + spec.setType(StateImpl.OSGI_EE_NAMESPACE); + try { + FilterImpl filter = FilterImpl.newInstance(filterSpec); + spec.setMatchingFilter(filter); + String name = filter.getPrimaryKeyValue(spec.getType()); + if (name != null) + spec.setName(name); + } catch (InvalidSyntaxException e) { + throw new BundleException("Error converting required execution environment.", e); //$NON-NLS-1$ + } + result.add(spec); + return result; + } + + private static String createOSGiEERequirementFilter(String bree) throws BundleException { + String[] nameVersion = getOSGiEENameVersion(bree); + String eeName = nameVersion[0]; + String v = nameVersion[1]; + String filterSpec; + if (v == null) + filterSpec = "(osgi.ee=" + eeName + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + else + filterSpec = "(&(osgi.ee=" + eeName + ")(version=" + v + "))"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + try { + // do a sanity check + FilterImpl.newInstance(filterSpec); + } catch (InvalidSyntaxException e) { + filterSpec = "(osgi.ee=" + bree + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + try { + // do another sanity check + FilterImpl.newInstance(filterSpec); + } catch (InvalidSyntaxException e1) { + throw new BundleException("Error converting required execution environment.", e1); //$NON-NLS-1$ + } + } + return filterSpec; + } + + static String[] getOSGiEENameVersion(String bree) { + String ee1 = null; + String ee2 = null; + String v1 = null; + String v2 = null; + int separator = bree.indexOf('/'); + if (separator <= 0 || separator == bree.length() - 1) { + ee1 = bree; + } else { + ee1 = bree.substring(0, separator); + ee2 = bree.substring(separator + 1); + } + int v1idx = ee1.indexOf('-'); + if (v1idx > 0 && v1idx < ee1.length() - 1) { + // check for > 0 to avoid EEs starting with - + // check for < len - 1 to avoid ending with - + try { + v1 = ee1.substring(v1idx + 1); + // sanity check version format + Version.parseVersion(v1); + ee1 = ee1.substring(0, v1idx); + } catch (IllegalArgumentException e) { + v1 = null; + } + } + + int v2idx = ee2 == null ? -1 : ee2.indexOf('-'); + if (v2idx > 0 && v2idx < ee2.length() - 1) { + // check for > 0 to avoid EEs starting with - + // check for < len - 1 to avoid ending with - + try { + v2 = ee2.substring(v2idx + 1); + Version.parseVersion(v2); + ee2 = ee2.substring(0, v2idx); + } catch (IllegalArgumentException e) { + v2 = null; + } + } + + if (v1 == null) + v1 = v2; + if (v1 != null && v2 != null && !v1.equals(v2)) { + ee1 = bree; + ee2 = null; + v1 = null; + v2 = null; + } + if ("J2SE".equals(ee1)) //$NON-NLS-1$ + ee1 = "JavaSE"; //$NON-NLS-1$ + if ("J2SE".equals(ee2)) //$NON-NLS-1$ + ee2 = "JavaSE"; //$NON-NLS-1$ + + String eeName = ee1 + (ee2 == null ? "" : '/' + ee2); //$NON-NLS-1$ + + return new String[] {eeName, v1}; + } + + static List<GenericSpecification> createOSGiRequires(ManifestElement[] osgiRequires, List<GenericSpecification> result) throws BundleException { + if (osgiRequires == null) + return result; + if (result == null) + result = new ArrayList<GenericSpecification>(); + for (ManifestElement element : osgiRequires) { + String[] namespaces = element.getValueComponents(); + for (String namespace : namespaces) { + GenericSpecificationImpl spec = new GenericSpecificationImpl(); + spec.setType(namespace); + String filterSpec = element.getDirective(Constants.FILTER_DIRECTIVE); + if (filterSpec != null) { + try { + FilterImpl filter = FilterImpl.newInstance(filterSpec); + spec.setMatchingFilter(filter); + String name = filter.getPrimaryKeyValue(namespace); + if (name != null) + spec.setName(name); + } catch (InvalidSyntaxException e) { + String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, Constants.REQUIRE_CAPABILITY, element.toString()); + throw new BundleException(message + " : filter", BundleException.MANIFEST_ERROR, e); //$NON-NLS-1$ + } + } + String resolutionDirective = element.getDirective(Constants.RESOLUTION_DIRECTIVE); + int resolution = 0; + if (Constants.RESOLUTION_OPTIONAL.equals(resolutionDirective)) + resolution |= GenericSpecification.RESOLUTION_OPTIONAL; + String cardinality = element.getDirective(Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE); + if (Namespace.CARDINALITY_MULTIPLE.equals(cardinality)) + resolution |= GenericSpecification.RESOLUTION_MULTIPLE; + spec.setResolution(resolution); + spec.setAttributes(getAttributes(element, DEFINED_REQUIRE_CAPABILITY_ATTRS)); + spec.setArbitraryDirectives(getDirectives(element, DEFINED_REQUIRE_CAPABILITY_DIRECTIVES)); + result.add(spec); + } + } + return result; + } + + private static List<GenericSpecification> createEquinoxRequires(ManifestElement[] equinoxRequires) throws BundleException { + if (equinoxRequires == null) + return null; + ArrayList<GenericSpecification> results = new ArrayList<GenericSpecification>(equinoxRequires.length); + for (int i = 0; i < equinoxRequires.length; i++) { + String[] genericNames = equinoxRequires[i].getValueComponents(); + for (int j = 0; j < genericNames.length; j++) { + GenericSpecificationImpl spec = new GenericSpecificationImpl(); + int colonIdx = genericNames[j].indexOf(':'); + if (colonIdx > 0) { + spec.setName(genericNames[j].substring(0, colonIdx)); + spec.setType(genericNames[j].substring(colonIdx + 1)); + } else + spec.setName(genericNames[j]); + try { + spec.setMatchingFilter(equinoxRequires[i].getAttribute(Constants.SELECTION_FILTER_ATTRIBUTE), true); + } catch (InvalidSyntaxException e) { + String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, GENERIC_REQUIRE, equinoxRequires[i].toString()); + throw new BundleException(message + " : " + Constants.SELECTION_FILTER_ATTRIBUTE, BundleException.MANIFEST_ERROR, e); //$NON-NLS-1$ + } + String optional = equinoxRequires[i].getAttribute(OPTIONAL_ATTR); + String multiple = equinoxRequires[i].getAttribute(MULTIPLE_ATTR); + int resolution = 0; + if (TRUE.equals(optional)) + resolution |= GenericSpecification.RESOLUTION_OPTIONAL; + if (TRUE.equals(multiple)) + resolution |= GenericSpecification.RESOLUTION_MULTIPLE; + spec.setResolution(resolution); + results.add(spec); + } + } + return results; + } + + private static GenericDescription[] createGenericCapabilities(ManifestElement[] equinoxCapabilities, ManifestElement[] osgiCapabilities, BundleDescription description) throws BundleException { + List<GenericDescription> result = createEquinoxCapabilities(equinoxCapabilities); + result = createOSGiCapabilities(osgiCapabilities, result, description); + return result == null ? null : result.toArray(new GenericDescription[result.size()]); + } + + static List<GenericDescription> createOSGiCapabilities(ManifestElement[] osgiCapabilities, List<GenericDescription> result, BundleDescription description) throws BundleException { + if (result == null) + result = new ArrayList<GenericDescription>(osgiCapabilities == null ? 1 : osgiCapabilities.length + 1); + // Always have an osgi.identity capability if there is a symbolic name. + GenericDescription osgiIdentity = createOsgiIdentityCapability(description); + if (osgiIdentity != null) + // always add the capability to the front + result.add(0, osgiIdentity); + return createOSGiCapabilities(osgiCapabilities, result, (Integer) null); + } + + static List<GenericDescription> createOSGiCapabilities(ManifestElement[] osgiCapabilities, List<GenericDescription> result, Integer profileIndex) throws BundleException { + if (osgiCapabilities == null) + return result; + if (result == null) + result = new ArrayList<GenericDescription>(osgiCapabilities.length); + + for (ManifestElement element : osgiCapabilities) { + String[] namespaces = element.getValueComponents(); + for (String namespace : namespaces) { + if (IdentityNamespace.IDENTITY_NAMESPACE.equals(namespace)) + throw new BundleException("A bundle is not allowed to define a capability in the " + IdentityNamespace.IDENTITY_NAMESPACE + " name space."); //$NON-NLS-1$ //$NON-NLS-2$ + + GenericDescriptionImpl desc = new GenericDescriptionImpl(); + desc.setType(namespace); + Map<String, Object> mapAttrs = getAttributes(element, new String[0]); + if (profileIndex != null) + mapAttrs.put(ExportPackageDescriptionImpl.EQUINOX_EE, profileIndex); + Dictionary<String, Object> attrs = mapAttrs == null ? new Hashtable<String, Object>() : new Hashtable<String, Object>(mapAttrs); + desc.setAttributes(attrs); + Map<String, String> directives = new HashMap<String, String>(); + Enumeration<String> keys = element.getDirectiveKeys(); + if (keys != null) + for (keys = element.getDirectiveKeys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + directives.put(key, element.getDirective(key)); + } + desc.setDirectives(directives); + result.add(desc); + } + } + return result; + } + + private static List<GenericDescription> createEquinoxCapabilities(ManifestElement[] equinoxCapabilities) throws BundleException { + if (equinoxCapabilities == null) + return null; + ArrayList<GenericDescription> results = new ArrayList<GenericDescription>(equinoxCapabilities.length); + for (int i = 0; i < equinoxCapabilities.length; i++) { + String[] genericNames = equinoxCapabilities[i].getValueComponents(); + for (int j = 0; j < genericNames.length; j++) { + GenericDescriptionImpl desc = new GenericDescriptionImpl(); + String name = genericNames[j]; + int colonIdx = genericNames[j].indexOf(':'); + if (colonIdx > 0) { + name = genericNames[j].substring(0, colonIdx); + desc.setType(genericNames[j].substring(colonIdx + 1)); + if (IdentityNamespace.IDENTITY_NAMESPACE.equals(desc.getType())) + throw new BundleException("A bundle is not allowed to define a capability in the " + IdentityNamespace.IDENTITY_NAMESPACE + " name space."); //$NON-NLS-1$ //$NON-NLS-2$ + } + Map<String, Object> mapAttrs = getAttributes(equinoxCapabilities[i], new String[] {Constants.VERSION_ATTRIBUTE}); + Dictionary<String, Object> attrs = mapAttrs == null ? new Hashtable<String, Object>() : new Hashtable<String, Object>(mapAttrs); + attrs.put(desc.getType(), name); + String versionString = equinoxCapabilities[i].getAttribute(Constants.VERSION_ATTRIBUTE); + if (versionString != null) + attrs.put(Constants.VERSION_ATTRIBUTE, Version.parseVersion(versionString)); + desc.setAttributes(attrs); + results.add(desc); + } + } + return results; + } + + private static NativeCodeSpecification createNativeCode(ManifestElement[] nativeCode) throws BundleException { + if (nativeCode == null) + return null; + NativeCodeSpecificationImpl result = new NativeCodeSpecificationImpl(); + result.setName(Constants.BUNDLE_NATIVECODE); + int length = nativeCode.length; + if (length > 0 && nativeCode[length - 1].getValue().equals("*")) { //$NON-NLS-1$ + result.setOptional(true); + length--; + } + NativeCodeDescriptionImpl[] suppliers = new NativeCodeDescriptionImpl[length]; + for (int i = 0; i < length; i++) { + suppliers[i] = createNativeCodeDescription(nativeCode[i]); + } + result.setPossibleSuppliers(suppliers); + return result; + } + + private static NativeCodeDescriptionImpl createNativeCodeDescription(ManifestElement manifestElement) throws BundleException { + NativeCodeDescriptionImpl result = new NativeCodeDescriptionImpl(); + result.setName(Constants.BUNDLE_NATIVECODE); + result.setNativePaths(manifestElement.getValueComponents()); + result.setOSNames(manifestElement.getAttributes(Constants.BUNDLE_NATIVECODE_OSNAME)); + result.setProcessors(manifestElement.getAttributes(Constants.BUNDLE_NATIVECODE_PROCESSOR)); + result.setOSVersions(createVersionRanges(manifestElement.getAttributes(Constants.BUNDLE_NATIVECODE_OSVERSION))); + result.setLanguages(manifestElement.getAttributes(Constants.BUNDLE_NATIVECODE_LANGUAGE)); + try { + result.setFilter(manifestElement.getAttribute(Constants.SELECTION_FILTER_ATTRIBUTE)); + } catch (InvalidSyntaxException e) { + String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, Constants.BUNDLE_NATIVECODE, manifestElement.toString()); + throw new BundleException(message + " : " + Constants.SELECTION_FILTER_ATTRIBUTE, BundleException.MANIFEST_ERROR, e); //$NON-NLS-1$ + } + return result; + } + + private static VersionRange[] createVersionRanges(String[] ranges) { + if (ranges == null) + return null; + VersionRange[] result = new VersionRange[ranges.length]; + for (int i = 0; i < result.length; i++) + result[i] = new VersionRange(ranges[i]); + return result; + } + + private static VersionRange getVersionRange(String versionRange) { + if (versionRange == null) + return null; + return new VersionRange(versionRange); + } + + public static void checkImportExportSyntax(String headerKey, ManifestElement[] elements, boolean export, boolean dynamic, boolean jreBundle) throws BundleException { + if (elements == null) + return; + int length = elements.length; + Set<String> packages = new HashSet<String>(length); + for (int i = 0; i < length; i++) { + // check for duplicate imports + String[] packageNames = elements[i].getValueComponents(); + for (int j = 0; j < packageNames.length; j++) { + if (!export && !dynamic && packages.contains(packageNames[j])) { + String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, headerKey, elements[i].toString()); + throw new BundleException(message + " : " + NLS.bind(StateMsg.HEADER_PACKAGE_DUPLICATES, packageNames[j]), BundleException.MANIFEST_ERROR); //$NON-NLS-1$ + } + // check for java.* + if (!jreBundle && packageNames[j].startsWith("java.")) { //$NON-NLS-1$ + String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, headerKey, elements[i].toString()); + throw new BundleException(message + " : " + NLS.bind(StateMsg.HEADER_PACKAGE_JAVA, packageNames[j]), BundleException.MANIFEST_ERROR); //$NON-NLS-1$ + } + packages.add(packageNames[j]); + } + // check for version/specification version mismatch + String version = elements[i].getAttribute(Constants.VERSION_ATTRIBUTE); + if (version != null) { + String specVersion = elements[i].getAttribute(Constants.PACKAGE_SPECIFICATION_VERSION); + if (specVersion != null && !specVersion.equals(version)) + throw new BundleException(NLS.bind(StateMsg.HEADER_VERSION_ERROR, Constants.VERSION_ATTRIBUTE, Constants.PACKAGE_SPECIFICATION_VERSION), BundleException.MANIFEST_ERROR); + } + // check for bundle-symbolic-name and bundle-verion attibures + // (failure) + if (export) { + if (elements[i].getAttribute(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE) != null) { + String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, headerKey, elements[i].toString()); + throw new BundleException(message + " : " + NLS.bind(StateMsg.HEADER_EXPORT_ATTR_ERROR, Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, Constants.EXPORT_PACKAGE), BundleException.MANIFEST_ERROR); //$NON-NLS-1$ + } + if (elements[i].getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE) != null) { + String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, headerKey, elements[i].toString()); + throw new BundleException(NLS.bind(message + " : " + StateMsg.HEADER_EXPORT_ATTR_ERROR, Constants.BUNDLE_VERSION_ATTRIBUTE, Constants.EXPORT_PACKAGE), BundleException.MANIFEST_ERROR); //$NON-NLS-1$ + } + } + } + } + + private static void checkForDuplicateDirectivesAttributes(String headerKey, ManifestElement[] elements) throws BundleException { + // check for duplicate directives + for (int i = 0; i < elements.length; i++) { + Enumeration<String> directiveKeys = elements[i].getDirectiveKeys(); + if (directiveKeys != null) { + while (directiveKeys.hasMoreElements()) { + String key = directiveKeys.nextElement(); + String[] directives = elements[i].getDirectives(key); + if (directives.length > 1) { + String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, headerKey, elements[i].toString()); + throw new BundleException(NLS.bind(message + " : " + StateMsg.HEADER_DIRECTIVE_DUPLICATES, key), BundleException.MANIFEST_ERROR); //$NON-NLS-1$ + } + } + } + Enumeration<String> attrKeys = elements[i].getKeys(); + if (attrKeys != null) { + while (attrKeys.hasMoreElements()) { + String key = attrKeys.nextElement(); + String[] attrs = elements[i].getAttributes(key); + if (attrs.length > 1) { + String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, headerKey, elements[i].toString()); + throw new BundleException(message + " : " + NLS.bind(StateMsg.HEADER_ATTRIBUTE_DUPLICATES, key), BundleException.MANIFEST_ERROR); //$NON-NLS-1$ + } + } + } + } + } + + private static void checkExtensionBundle(String headerKey, ManifestElement[] elements) throws BundleException { + if (elements.length == 0 || elements[0].getDirective(Constants.EXTENSION_DIRECTIVE) == null) + return; + String hostName = elements[0].getValue(); + // XXX: The extension bundle check is done against system.bundle and org.eclipse.osgi + if (!hostName.equals(Constants.SYSTEM_BUNDLE_SYMBOLICNAME) && !hostName.equals(EquinoxContainer.NAME)) { + String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, headerKey, elements[0].toString()); + throw new BundleException(message + " : " + NLS.bind(StateMsg.HEADER_EXTENSION_ERROR, hostName), BundleException.MANIFEST_ERROR); //$NON-NLS-1$ + } + } + + static GenericDescription createOsgiIdentityCapability(BundleDescription description) { + if (description.getSymbolicName() == null) + return null; + GenericDescriptionImpl result = new GenericDescriptionImpl(); + result.setType(IdentityNamespace.IDENTITY_NAMESPACE); + Dictionary<String, Object> attributes = new Hashtable<String, Object>(description.getDeclaredAttributes()); + // remove osgi.wiring.bundle and bundle-version attributes + attributes.remove(BundleNamespace.BUNDLE_NAMESPACE); + attributes.remove(Constants.BUNDLE_VERSION_ATTRIBUTE); + attributes.put(IdentityNamespace.IDENTITY_NAMESPACE, description.getSymbolicName()); + attributes.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, description.getHost() == null ? IdentityNamespace.TYPE_BUNDLE : IdentityNamespace.TYPE_FRAGMENT); + attributes.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, description.getVersion()); + result.setAttributes(attributes); + Map<String, String> directives = new HashMap<String, String>(description.getDeclaredDirectives()); + // remove defaults directive values + if (!description.isSingleton()) + directives.remove(Constants.SINGLETON_DIRECTIVE); + if (description.attachFragments() && description.dynamicFragments()) + directives.remove(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE); + result.setDirectives(directives); + return result; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateDeltaImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateDeltaImpl.java new file mode 100644 index 000000000..966a0224d --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateDeltaImpl.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import java.util.*; +import org.eclipse.osgi.service.resolver.*; + +/** + * This class is threadsafe. + * + */ +final class StateDeltaImpl implements StateDelta { + + private final State state; + + private final Map<BundleDescription, BundleDelta> changes = new HashMap<BundleDescription, BundleDelta>(); + private ResolverHookException error; + + public StateDeltaImpl(State state) { + this.state = state; + } + + public BundleDelta[] getChanges() { + synchronized (this.changes) { + return changes.values().toArray(new BundleDelta[changes.size()]); + } + } + + public BundleDelta[] getChanges(int mask, boolean exact) { + synchronized (this.changes) { + List<BundleDelta> result = new ArrayList<BundleDelta>(); + for (Iterator<BundleDelta> changesIter = changes.values().iterator(); changesIter.hasNext();) { + BundleDelta change = changesIter.next(); + if (mask == change.getType() || (!exact && (change.getType() & mask) != 0)) + result.add(change); + } + return result.toArray(new BundleDelta[result.size()]); + } + } + + public State getState() { + return state; + } + + public ResolverHookException getResovlerHookException() { + return error; + } + + void setResolverHookException(ResolverHookException error) { + this.error = error; + } + + void recordBundleAdded(BundleDescriptionImpl added) { + synchronized (this.changes) { + BundleDeltaImpl change = (BundleDeltaImpl) changes.get(added); + if (change == null) { + changes.put(added, new BundleDeltaImpl(added, BundleDelta.ADDED)); + return; + } + if (change.getType() == BundleDelta.REMOVED) { + changes.remove(added); + return; + } + int newType = change.getType(); + if ((newType & BundleDelta.REMOVED) != 0) + newType &= ~BundleDelta.REMOVED; + change.setType(newType | BundleDelta.ADDED); + change.setBundle(added); + } + } + + void recordBundleUpdated(BundleDescriptionImpl updated) { + synchronized (this.changes) { + BundleDeltaImpl change = (BundleDeltaImpl) changes.get(updated); + if (change == null) { + changes.put(updated, new BundleDeltaImpl(updated, BundleDelta.UPDATED)); + return; + } + if ((change.getType() & (BundleDelta.ADDED | BundleDelta.REMOVED)) != 0) + return; + change.setType(change.getType() | BundleDelta.UPDATED); + change.setBundle(updated); + } + } + + void recordBundleRemoved(BundleDescriptionImpl removed) { + synchronized (this.changes) { + BundleDeltaImpl change = (BundleDeltaImpl) changes.get(removed); + if (change == null) { + changes.put(removed, new BundleDeltaImpl(removed, BundleDelta.REMOVED)); + return; + } + if (change.getType() == BundleDelta.ADDED) { + changes.remove(removed); + return; + } + int newType = change.getType(); + if ((newType & BundleDelta.ADDED) != 0) + newType &= ~BundleDelta.ADDED; + change.setType(newType | BundleDelta.REMOVED); + } + } + + void recordBundleRemovalPending(BundleDescriptionImpl removed) { + synchronized (this.changes) { + BundleDeltaImpl change = (BundleDeltaImpl) changes.get(removed); + if (change == null) { + changes.put(removed, new BundleDeltaImpl(removed, BundleDelta.REMOVAL_PENDING)); + return; + } + int newType = change.getType(); + if ((newType & BundleDelta.REMOVAL_COMPLETE) != 0) + newType &= ~BundleDelta.REMOVAL_COMPLETE; + change.setType(newType | BundleDelta.REMOVAL_PENDING); + } + } + + void recordBundleRemovalComplete(BundleDescriptionImpl removed) { + synchronized (this.changes) { + BundleDeltaImpl change = (BundleDeltaImpl) changes.get(removed); + if (change == null) { + changes.put(removed, new BundleDeltaImpl(removed, BundleDelta.REMOVAL_COMPLETE)); + return; + } + int newType = change.getType(); + if ((newType & BundleDelta.REMOVAL_PENDING) != 0) + newType &= ~BundleDelta.REMOVAL_PENDING; + change.setType(newType | BundleDelta.REMOVAL_COMPLETE); + } + } + + void recordBundleResolved(BundleDescriptionImpl resolved, boolean result) { + synchronized (this.changes) { + if (resolved.isResolved() == result) + return; // do not record anything if nothing has changed + BundleDeltaImpl change = (BundleDeltaImpl) changes.get(resolved); + int newType = result ? BundleDelta.RESOLVED : BundleDelta.UNRESOLVED; + if (change == null) { + change = new BundleDeltaImpl(resolved, newType); + changes.put(resolved, change); + return; + } + // new type will have only one of RESOLVED|UNRESOLVED bits set + newType = newType | (change.getType() & ~(BundleDelta.RESOLVED | BundleDelta.UNRESOLVED)); + change.setType(newType); + change.setBundle(resolved); + } + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateHelperImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateHelperImpl.java new file mode 100644 index 000000000..e6cc72a7e --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateHelperImpl.java @@ -0,0 +1,646 @@ +/******************************************************************************* + * Copyright (c) 2004, 2012 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import java.util.*; +import org.eclipse.osgi.internal.baseadaptor.ArrayMap; +import org.eclipse.osgi.internal.container.ComputeNodeOrder; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.Constants; +import org.osgi.framework.hooks.resolver.ResolverHook; +import org.osgi.framework.hooks.resolver.ResolverHookFactory; +import org.osgi.framework.namespace.ExecutionEnvironmentNamespace; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleRevision; + +/** + * An implementation for the StateHelper API. Access to this implementation is + * provided by the PlatformAdmin. Since this helper is a general facility for + * state manipulation, it should not be tied to any implementation details. + */ +public final class StateHelperImpl implements StateHelper { + private static final StateHelper instance = new StateHelperImpl(); + + /** + * @see StateHelper + */ + public BundleDescription[] getDependentBundles(BundleDescription[] bundles) { + if (bundles == null || bundles.length == 0) + return new BundleDescription[0]; + + Set<BundleDescription> reachable = new HashSet<BundleDescription>(bundles.length); + for (int i = 0; i < bundles.length; i++) { + if (!bundles[i].isResolved()) + continue; + addDependentBundles(bundles[i], reachable); + } + return reachable.toArray(new BundleDescription[reachable.size()]); + } + + private void addDependentBundles(BundleDescription bundle, Set<BundleDescription> reachable) { + if (reachable.contains(bundle)) + return; + reachable.add(bundle); + BundleDescription[] dependents = bundle.getDependents(); + for (int i = 0; i < dependents.length; i++) + addDependentBundles(dependents[i], reachable); + } + + public BundleDescription[] getPrerequisites(BundleDescription[] bundles) { + if (bundles == null || bundles.length == 0) + return new BundleDescription[0]; + Set<BundleDescription> reachable = new HashSet<BundleDescription>(bundles.length); + for (int i = 0; i < bundles.length; i++) + addPrerequisites(bundles[i], reachable); + return reachable.toArray(new BundleDescription[reachable.size()]); + } + + private void addPrerequisites(BundleDescription bundle, Set<BundleDescription> reachable) { + if (reachable.contains(bundle)) + return; + reachable.add(bundle); + List<BundleDescription> depList = ((BundleDescriptionImpl) bundle).getBundleDependencies(); + BundleDescription[] dependencies = depList.toArray(new BundleDescription[depList.size()]); + for (int i = 0; i < dependencies.length; i++) + addPrerequisites(dependencies[i], reachable); + } + + private Map<String, List<ExportPackageDescription>> getExportedPackageMap(State state) { + Map<String, List<ExportPackageDescription>> result = new HashMap<String, List<ExportPackageDescription>>(); + BundleDescription[] bundles = state.getBundles(); + for (int i = 0; i < bundles.length; i++) { + ExportPackageDescription[] packages = bundles[i].getExportPackages(); + for (int j = 0; j < packages.length; j++) { + ExportPackageDescription description = packages[j]; + List<ExportPackageDescription> exports = result.get(description.getName()); + if (exports == null) { + exports = new ArrayList<ExportPackageDescription>(); + result.put(description.getName(), exports); + } + exports.add(description); + } + } + return result; + } + + private Map<String, List<GenericDescription>> getGenericsMap(State state, boolean resolved) { + Map<String, List<GenericDescription>> result = new HashMap<String, List<GenericDescription>>(); + BundleDescription[] bundles = state.getBundles(); + for (int i = 0; i < bundles.length; i++) { + if (resolved && !bundles[i].isResolved()) + continue; // discard unresolved bundles + GenericDescription[] generics = bundles[i].getGenericCapabilities(); + for (int j = 0; j < generics.length; j++) { + GenericDescription description = generics[j]; + List<GenericDescription> genericList = result.get(description.getName()); + if (genericList == null) { + genericList = new ArrayList<GenericDescription>(1); + result.put(description.getName(), genericList); + } + genericList.add(description); + } + } + return result; + } + + private VersionConstraint[] getUnsatisfiedLeaves(State state, BundleDescription[] bundles, ResolverHook hook) { + Map<String, List<ExportPackageDescription>> packages = getExportedPackageMap(state); + Map<String, List<GenericDescription>> generics = getGenericsMap(state, false); + Set<VersionConstraint> result = new HashSet<VersionConstraint>(); + List<BundleDescription> bundleList = new ArrayList<BundleDescription>(bundles.length); + for (int i = 0; i < bundles.length; i++) + bundleList.add(bundles[i]); + for (int i = 0; i < bundleList.size(); i++) { + BundleDescription description = bundleList.get(i); + VersionConstraint[] constraints = getUnsatisfiedConstraints(description, hook); + for (int j = 0; j < constraints.length; j++) { + VersionConstraint constraint = constraints[j]; + Collection<BaseDescription> satisfied = null; + if (constraint instanceof BundleSpecification || constraint instanceof HostSpecification) { + BundleDescription[] suppliers = state.getBundles(constraint.getName()); + satisfied = getPossibleCandidates(constraint, suppliers, constraint instanceof HostSpecification ? BundleRevision.HOST_NAMESPACE : null, hook, false); + } else if (constraint instanceof ImportPackageSpecification) { + List<ExportPackageDescription> exports = packages.get(constraint.getName()); + if (exports != null) + satisfied = getPossibleCandidates(constraint, exports.toArray(new BaseDescription[exports.size()]), null, hook, false); + } else if (constraint instanceof GenericSpecification) { + List<GenericDescription> genericSet = generics.get(constraint.getName()); + if (genericSet != null) + satisfied = getPossibleCandidates(constraint, genericSet.toArray(new BaseDescription[genericSet.size()]), null, hook, false); + } + if (satisfied == null || satisfied.isEmpty()) { + result.add(constraint); + } else { + for (BaseDescription baseDescription : satisfied) { + if (!baseDescription.getSupplier().isResolved() && !bundleList.contains(baseDescription.getSupplier())) { + bundleList.add(baseDescription.getSupplier()); + // TODO only add the first supplier that is not resolved; + // this is the previous behavior before the fix for bug 333071; should consider adding all unresolved + break; + } + } + } + } + } + return result.toArray(new VersionConstraint[result.size()]); + + } + + public VersionConstraint[] getUnsatisfiedLeaves(BundleDescription[] bundles) { + if (bundles.length == 0) + return new VersionConstraint[0]; + State state = bundles[0].getContainingState(); + ResolverHook hook = beginHook(state, Arrays.asList((BundleRevision[]) bundles)); + try { + return getUnsatisfiedLeaves(state, bundles, hook); + } finally { + if (hook != null) + hook.end(); + } + } + + private ResolverHook beginHook(State state, Collection<BundleRevision> triggers) { + if (!(state instanceof StateImpl)) + return null; + ResolverHookFactory hookFactory = ((StateImpl) state).getResolverHookFactory(); + return hookFactory == null ? null : hookFactory.begin(triggers); + } + + /** + * @see StateHelper + */ + public VersionConstraint[] getUnsatisfiedConstraints(BundleDescription bundle) { + ResolverHook hook = beginHook(bundle.getContainingState(), Arrays.asList(new BundleRevision[] {bundle})); + try { + return getUnsatisfiedConstraints(bundle, hook); + } finally { + if (hook != null) + hook.end(); + } + } + + private VersionConstraint[] getUnsatisfiedConstraints(BundleDescription bundle, ResolverHook hook) { + State containingState = bundle.getContainingState(); + if (containingState == null) + // it is a bug in the client to call this method when not attached to a state + throw new IllegalStateException("Does not belong to a state"); //$NON-NLS-1$ + List<VersionConstraint> unsatisfied = new ArrayList<VersionConstraint>(); + HostSpecification host = bundle.getHost(); + if (host != null) + if (!host.isResolved() && !isBundleConstraintResolvable(host, BundleRevision.HOST_NAMESPACE, hook)) + unsatisfied.add(host); + BundleSpecification[] requiredBundles = bundle.getRequiredBundles(); + for (int i = 0; i < requiredBundles.length; i++) + if (!requiredBundles[i].isResolved() && !isBundleConstraintResolvable(requiredBundles[i], null, hook)) + unsatisfied.add(requiredBundles[i]); + ImportPackageSpecification[] packages = bundle.getImportPackages(); + for (int i = 0; i < packages.length; i++) + if (!packages[i].isResolved() && !isResolvable(packages[i], hook)) { + if (bundle.isResolved()) { + // if the bundle is resolved the check if the import is option. + // Here we assume that an unresolved mandatory import must have been dropped + // in favor of an export from the same bundle (bug 338240) + if (!ImportPackageSpecification.RESOLUTION_OPTIONAL.equals(packages[i].getDirective(Constants.RESOLUTION_DIRECTIVE))) + continue; + } + unsatisfied.add(packages[i]); + } + GenericSpecification[] generics = bundle.getGenericRequires(); + for (int i = 0; i < generics.length; i++) + if (!generics[i].isResolved() && !isResolvable(generics[i], hook)) + unsatisfied.add(generics[i]); + NativeCodeSpecification nativeCode = bundle.getNativeCodeSpecification(); + if (nativeCode != null && !nativeCode.isResolved()) + unsatisfied.add(nativeCode); + return unsatisfied.toArray(new VersionConstraint[unsatisfied.size()]); + } + + private ArrayMap<BundleCapability, BaseDescription> asArrayMap(List<BaseDescription> descriptions, String namespace) { + List<BundleCapability> capabilities = new ArrayList<BundleCapability>(descriptions.size()); + for (BaseDescription description : descriptions) + capabilities.add(((BaseDescriptionImpl) description).getCapability(namespace)); + return new ArrayMap<BundleCapability, BaseDescription>(capabilities, descriptions); + } + + private List<BaseDescription> getPossibleCandidates(VersionConstraint constraint, BaseDescription[] descriptions, String namespace, ResolverHook hook, boolean resolved) { + List<BaseDescription> candidates = new ArrayList<BaseDescription>(); + for (int i = 0; i < descriptions.length; i++) + if ((!resolved || descriptions[i].getSupplier().isResolved()) && constraint.isSatisfiedBy(descriptions[i])) + candidates.add(descriptions[i]); + if (hook != null) + hook.filterMatches(constraint.getRequirement(), asArrayMap(candidates, namespace)); + return candidates; + } + + /** + * @see StateHelper + */ + public boolean isResolvable(ImportPackageSpecification constraint) { + ResolverHook hook = beginHook(constraint.getBundle().getContainingState(), Arrays.asList(new BundleRevision[] {constraint.getBundle()})); + try { + return isResolvable(constraint, hook); + } finally { + if (hook != null) + hook.end(); + } + } + + private boolean isResolvable(ImportPackageSpecification constraint, ResolverHook hook) { + ExportPackageDescription[] exports = constraint.getBundle().getContainingState().getExportedPackages(); + return getPossibleCandidates(constraint, exports, null, hook, true).size() > 0; + } + + private boolean isResolvable(GenericSpecification constraint, ResolverHook hook) { + Map<String, List<GenericDescription>> genericCapabilities = getGenericsMap(constraint.getBundle().getContainingState(), true); + List<GenericDescription> genericList = genericCapabilities.get(constraint.getName()); + if (genericList == null) + return false; + return getPossibleCandidates(constraint, genericList.toArray(new BaseDescription[genericList.size()]), null, hook, true).size() > 0; + } + + /** + * @see StateHelper + */ + public boolean isResolvable(BundleSpecification specification) { + return isBundleConstraintResolvable(specification, null); + } + + /** + * @see StateHelper + */ + public boolean isResolvable(HostSpecification specification) { + return isBundleConstraintResolvable(specification, BundleRevision.HOST_NAMESPACE); + } + + /* + * Returns whether a bundle specification/host specification can be resolved. + */ + private boolean isBundleConstraintResolvable(VersionConstraint constraint, String namespace) { + ResolverHook hook = beginHook(constraint.getBundle().getContainingState(), Arrays.asList(new BundleRevision[] {constraint.getBundle()})); + try { + return isBundleConstraintResolvable(constraint, namespace, hook); + } finally { + if (hook != null) + hook.end(); + } + } + + private boolean isBundleConstraintResolvable(VersionConstraint constraint, String namespace, ResolverHook hook) { + BundleDescription[] availableBundles = constraint.getBundle().getContainingState().getBundles(constraint.getName()); + return getPossibleCandidates(constraint, availableBundles, namespace, hook, true).size() > 0; + } + + public Object[][] sortBundles(BundleDescription[] toSort) { + List<Object[]> references = new ArrayList<Object[]>(toSort.length); + for (int i = 0; i < toSort.length; i++) + if (toSort[i].isResolved()) + buildReferences(toSort[i], references); + Object[][] cycles = ComputeNodeOrder.computeNodeOrder(toSort, references.toArray(new Object[references.size()][])); + if (cycles.length == 0) + return cycles; + // fix up host/fragment orders (bug 184127) + for (int i = 0; i < cycles.length; i++) { + for (int j = 0; j < cycles[i].length; j++) { + BundleDescription fragment = (BundleDescription) cycles[i][j]; + if (fragment.getHost() == null) + continue; + BundleDescription host = (BundleDescription) fragment.getHost().getSupplier(); + if (host == null) + continue; + fixFragmentOrder(host, fragment, toSort); + } + } + return cycles; + } + + private void fixFragmentOrder(BundleDescription host, BundleDescription fragment, BundleDescription[] toSort) { + int hostIndex = -1; + int fragIndex = -1; + for (int i = 0; i < toSort.length && (hostIndex == -1 || fragIndex == -1); i++) { + if (toSort[i] == host) + hostIndex = i; + else if (toSort[i] == fragment) + fragIndex = i; + } + if (fragIndex > -1 && fragIndex < hostIndex) { + for (int i = fragIndex; i < hostIndex; i++) + toSort[i] = toSort[i + 1]; + toSort[hostIndex] = fragment; + } + } + + private void buildReferences(BundleDescription description, List<Object[]> references) { + HostSpecification host = description.getHost(); + // it is a fragment + if (host != null) { + // just create a dependencies for non-payload requirements (osgi.wiring.host and osgi.ee) + if (host.getHosts() != null) { + BundleDescription[] hosts = host.getHosts(); + for (int i = 0; i < hosts.length; i++) + if (hosts[i] != description) + references.add(new Object[] {description, hosts[i]}); + } + GenericDescription[] genericDependencies = description.getResolvedGenericRequires(); + for (GenericDescription dependency : genericDependencies) { + if (ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE.equals(dependency.getType())) { + references.add(new Object[] {description, dependency.getSupplier()}); + } + } + } else { + // it is a host + buildReferences(description, ((BundleDescriptionImpl) description).getBundleDependencies(), references); + } + } + + private void buildReferences(BundleDescription description, List<BundleDescription> dependencies, List<Object[]> references) { + for (Iterator<BundleDescription> iter = dependencies.iterator(); iter.hasNext();) + addReference(description, iter.next(), references); + } + + private void addReference(BundleDescription description, BundleDescription reference, List<Object[]> references) { + // build the reference from the description + if (description == reference || reference == null) + return; + BundleDescription[] fragments = reference.getFragments(); + for (int i = 0; i < fragments.length; i++) { + if (fragments[i].isResolved()) { + ExportPackageDescription[] exports = fragments[i].getExportPackages(); + if (exports.length > 0) + references.add(new Object[] {description, fragments[i]}); + } + } + references.add(new Object[] {description, reference}); + } + + public ExportPackageDescription[] getVisiblePackages(BundleDescription bundle) { + return getVisiblePackages(bundle, 0); + } + + public ExportPackageDescription[] getVisiblePackages(BundleDescription bundle, int options) { + StateImpl state = (StateImpl) bundle.getContainingState(); + boolean strict = false; + if (state != null) + strict = state.inStrictMode(); + BundleDescription host = (BundleDescription) (bundle.getHost() == null ? bundle : bundle.getHost().getSupplier()); + List<ExportPackageDescription> orderedPkgList = new ArrayList<ExportPackageDescription>(); // list of all ExportPackageDescriptions that are visible (ArrayList is used to keep order) + Set<ExportPackageDescription> pkgSet = new HashSet<ExportPackageDescription>(); + Set<String> importList = new HashSet<String>(); // list of package names which are directly imported + // get the list of directly imported packages first. + ImportsHolder imports = new ImportsHolder(bundle, options); + for (int i = 0; i < imports.getSize(); i++) { + ExportPackageDescription pkgSupplier = imports.getSupplier(i); + if (pkgSupplier == null || pkgSupplier.getExporter() == host) // do not return the bundle'sr own imports + continue; + if (!isSystemExport(pkgSupplier, options) && !pkgSet.contains(pkgSupplier)) { + orderedPkgList.add(pkgSupplier); + pkgSet.add(pkgSupplier); + } + // get the sources of the required bundles of the exporter + BundleSpecification[] requires = pkgSupplier.getExporter().getRequiredBundles(); + Set<BundleDescription> visited = new HashSet<BundleDescription>(); + visited.add(bundle); // always add self to prevent recursing into self + Set<String> importNames = new HashSet<String>(1); + importNames.add(imports.getName(i)); + for (int j = 0; j < requires.length; j++) { + BundleDescription bundleSupplier = (BundleDescription) requires[j].getSupplier(); + if (bundleSupplier != null) + getPackages(bundleSupplier, bundle.getSymbolicName(), importList, orderedPkgList, pkgSet, visited, strict, importNames, options); + } + importList.add(imports.getName(i)); // be sure to add to direct import list + + } + // now find all the packages that are visible from required bundles + RequiresHolder requires = new RequiresHolder(bundle, options); + Set<BundleDescription> visited = new HashSet<BundleDescription>(requires.getSize()); + visited.add(bundle); // always add self to prevent recursing into self + for (int i = 0; i < requires.getSize(); i++) { + BundleDescription bundleSupplier = requires.getSupplier(i); + if (bundleSupplier != null) + getPackages(bundleSupplier, bundle.getSymbolicName(), importList, orderedPkgList, pkgSet, visited, strict, null, options); + } + return orderedPkgList.toArray(new ExportPackageDescription[orderedPkgList.size()]); + } + + private void getPackages(BundleDescription requiredBundle, String symbolicName, Set<String> importList, List<ExportPackageDescription> orderedPkgList, Set<ExportPackageDescription> pkgSet, Set<BundleDescription> visited, boolean strict, Set<String> pkgNames, int options) { + if (visited.contains(requiredBundle)) + return; // prevent duplicate entries and infinate loops incase of cycles + visited.add(requiredBundle); + // add all the exported packages from the required bundle; take x-friends into account. + ExportPackageDescription[] substitutedExports = requiredBundle.getSubstitutedExports(); + ExportPackageDescription[] imports = requiredBundle.getResolvedImports(); + Set<String> substituteNames = null; // a temporary set used to scope packages we get from getPackages + for (int i = 0; i < substitutedExports.length; i++) { + if ((pkgNames == null || pkgNames.contains(substitutedExports[i].getName()))) { + for (int j = 0; j < imports.length; j++) { + if (substitutedExports[i].getName().equals(imports[j].getName()) && !pkgSet.contains(imports[j])) { + if (substituteNames == null) + substituteNames = new HashSet<String>(1); + else + substituteNames.clear(); + // substituteNames is a set of one package containing the single substitute we are trying to get the source for + substituteNames.add(substitutedExports[i].getName()); + getPackages(imports[j].getSupplier(), symbolicName, importList, orderedPkgList, pkgSet, new HashSet<BundleDescription>(0), strict, substituteNames, options); + } + } + } + } + importList = substitutedExports.length == 0 ? importList : new HashSet<String>(importList); + for (int i = 0; i < substitutedExports.length; i++) + // we add the package name to the import list to prevent required bundles from adding more sources + importList.add(substitutedExports[i].getName()); + + ExportPackageDescription[] exports = requiredBundle.getSelectedExports(); + HashSet<String> exportNames = new HashSet<String>(exports.length); // set is used to improve performance of duplicate check. + for (int i = 0; i < exports.length; i++) + if ((pkgNames == null || pkgNames.contains(exports[i].getName())) && !isSystemExport(exports[i], options) && isFriend(symbolicName, exports[i], strict) && !importList.contains(exports[i].getName()) && !pkgSet.contains(exports[i])) { + if (!exportNames.contains(exports[i].getName())) { + // only add the first export + orderedPkgList.add(exports[i]); + pkgSet.add(exports[i]); + exportNames.add(exports[i].getName()); + } + } + // now look for exports from the required bundle. + RequiresHolder requiredBundles = new RequiresHolder(requiredBundle, options); + for (int i = 0; i < requiredBundles.getSize(); i++) { + if (requiredBundles.getSupplier(i) == null) + continue; + if (requiredBundles.isExported(i)) { + // looking for a specific package and that package is exported by this bundle or adding all packages from a reexported bundle + getPackages(requiredBundles.getSupplier(i), symbolicName, importList, orderedPkgList, pkgSet, visited, strict, pkgNames, options); + } else if (exportNames.size() > 0) { + // adding any exports from required bundles which we also export + Set<BundleDescription> tmpVisited = new HashSet<BundleDescription>(); + getPackages(requiredBundles.getSupplier(i), symbolicName, importList, orderedPkgList, pkgSet, tmpVisited, strict, exportNames, options); + } + } + } + + private boolean isSystemExport(ExportPackageDescription export, int options) { + if ((options & VISIBLE_INCLUDE_EE_PACKAGES) != 0) + return false; + return ((Integer) export.getDirective(ExportPackageDescriptionImpl.EQUINOX_EE)).intValue() >= 0; + } + + private boolean isFriend(String consumerBSN, ExportPackageDescription export, boolean strict) { + if (!strict) + return true; // ignore friends rules if not in strict mode + String[] friends = (String[]) export.getDirective(StateImpl.FRIENDS_DIRECTIVE); + if (friends == null) + return true; // no x-friends means it is wide open + for (int i = 0; i < friends.length; i++) + if (friends[i].equals(consumerBSN)) + return true; // the consumer is a friend + return false; + } + + public int getAccessCode(BundleDescription bundle, ExportPackageDescription export) { + if (((Boolean) export.getDirective(StateImpl.INTERNAL_DIRECTIVE)).booleanValue()) + return ACCESS_DISCOURAGED; + if (!isFriend(bundle.getSymbolicName(), export, true)) // pass strict here so that x-friends is processed + return ACCESS_DISCOURAGED; + return ACCESS_ENCOURAGED; + } + + public static StateHelper getInstance() { + return instance; + } + +} + +/* + * This class is used to encapsulate the import packages of a bundle used by getVisiblePackages(). If the method is called with the option + * VISIBLE_INCLUDE_ALL_HOST_WIRES, it uses resolved import packages to find all visible packages by a bundle. Called without this option, + * it uses imported packages instead of resolved imported packages and does not consider resolved dynamic imports. + * ImportsHolder serves to hide which of these is used, so that the body of getVisiblePackages() does not become full of checks. + * + */ +class ImportsHolder { + private final ImportPackageSpecification[] importedPackages; + private final ExportPackageDescription[] resolvedImports; + private final boolean isUsingResolved; + + // Depending on the options used, either importedPackages or resolvedImports is initialize, but not both. + ImportsHolder(BundleDescription bundle, int options) { + isUsingResolved = (options & StateHelper.VISIBLE_INCLUDE_ALL_HOST_WIRES) != 0; + if (isUsingResolved) { + importedPackages = null; + resolvedImports = bundle.getResolvedImports(); + } else { + importedPackages = bundle.getImportPackages(); + resolvedImports = null; + } + } + + ExportPackageDescription getSupplier(int index) { + if (isUsingResolved) + return resolvedImports[index]; + return (ExportPackageDescription) importedPackages[index].getSupplier(); + } + + String getName(int index) { + if (isUsingResolved) + return resolvedImports[index].getName(); + return importedPackages[index].getName(); + } + + int getSize() { + if (isUsingResolved) + return resolvedImports.length; + return importedPackages.length; + } +} + +/* + * This class is used to encapsulate the required bundles by a bundle, used by getVisiblePackages(). If the method is called with the option + * VISIBLE_INCLUDE_ALL_HOST_WIRES, it uses resolved required bundles to find all visible packages by a bundle. Called without this option, + * it uses required bundles instead of resolved required bundles and does not consider the constraints from fragments. + * RequiresHolder serves to hide which of these is used. + */ +class RequiresHolder { + private final BundleSpecification[] requiredBundles; + private final BundleDescription[] resolvedRequires; + private final boolean isUsingResolved; + private final Map<BundleDescription, Boolean> resolvedBundlesExported; + + // Depending on the options used, either requiredBundles or resolvedRequires is initialize, but not both. + RequiresHolder(BundleDescription bundle, int options) { + isUsingResolved = (options & StateHelper.VISIBLE_INCLUDE_ALL_HOST_WIRES) != 0; + if (isUsingResolved) { + requiredBundles = null; + resolvedBundlesExported = new HashMap<BundleDescription, Boolean>(); + resolvedRequires = bundle.getResolvedRequires(); + determineRequiresVisibility(bundle); + } else { + requiredBundles = bundle.getRequiredBundles(); + resolvedBundlesExported = null; + resolvedRequires = null; + } + } + + BundleDescription getSupplier(int index) { + if (isUsingResolved) + return resolvedRequires[index]; + return (BundleDescription) requiredBundles[index].getSupplier(); + } + + boolean isExported(int index) { + if (isUsingResolved) + return resolvedBundlesExported.get(resolvedRequires[index]).booleanValue(); + return requiredBundles[index].isExported(); + } + + int getSize() { + if (isUsingResolved) + return resolvedRequires.length; + return requiredBundles.length; + } + + /* + * This method determines for all resolved required bundles if they are reexported. + * Fragment bundles are also considered. + */ + private void determineRequiresVisibility(BundleDescription bundle) { + BundleSpecification[] required = bundle.getRequiredBundles(); + Set<BundleDescription> resolved = new HashSet<BundleDescription>(); + + for (int i = 0; i < resolvedRequires.length; i++) { + resolved.add(resolvedRequires[i]); + } + + // Get the visibility of all directly required bundles + for (int i = 0; i < required.length; i++) { + if (required[i].getSupplier() != null) { + resolvedBundlesExported.put((BundleDescription) required[i].getSupplier(), new Boolean(required[i].isExported())); + resolved.remove(required[i].getSupplier()); + } + } + + BundleDescription[] fragments = bundle.getFragments(); + + // Get the visibility of resolved required bundles, which come from fragments + if (resolved.size() > 0) { + for (int i = 0; i < fragments.length; i++) { + BundleSpecification[] fragmentRequiredBundles = fragments[i].getRequiredBundles(); + for (int j = 0; j < fragmentRequiredBundles.length; j++) { + if (resolved.contains(fragmentRequiredBundles[j].getSupplier())) { + resolvedBundlesExported.put((BundleDescription) fragmentRequiredBundles[j].getSupplier(), new Boolean(fragmentRequiredBundles[j].isExported())); + resolved.remove(fragmentRequiredBundles[j].getSupplier()); + } + } + if (resolved.size() == 0) { + break; + } + } + } + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateImpl.java new file mode 100644 index 000000000..295605d4d --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateImpl.java @@ -0,0 +1,1340 @@ +/******************************************************************************* + * Copyright (c) 2003, 2012 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 + * Danail Nachev - ProSyst - bug 218625 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import org.eclipse.osgi.internal.framework.EquinoxContainer; +import org.eclipse.osgi.internal.framework.FilterImpl; + +import org.eclipse.osgi.internal.debug.Debug; +import org.eclipse.osgi.internal.debug.FrameworkDebugOptions; + +import java.util.*; +import org.eclipse.osgi.framework.util.*; +import org.eclipse.osgi.service.resolver.*; +import org.eclipse.osgi.util.ManifestElement; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; +import org.osgi.framework.hooks.resolver.ResolverHook; +import org.osgi.framework.hooks.resolver.ResolverHookFactory; +import org.osgi.framework.wiring.BundleRevision; + +public abstract class StateImpl implements State { + + public static final String ECLIPSE_PLATFORMFILTER = "Eclipse-PlatformFilter"; //$NON-NLS-1$ + public static final String Eclipse_JREBUNDLE = "Eclipse-JREBundle"; //$NON-NLS-1$ + /** + * Manifest Export-Package directive indicating that the exported package should only + * be made available when the resolver is not in strict mode. + */ + public static final String INTERNAL_DIRECTIVE = "x-internal"; //$NON-NLS-1$ + + /** + * Manifest Export-Package directive indicating that the exported package should only + * be made available to friends of the exporting bundle. + */ + public static final String FRIENDS_DIRECTIVE = "x-friends"; //$NON-NLS-1$ + + /** + * Manifest header (named "Provide-Package") + * identifying the packages name + * provided to other bundles which require the bundle. + * + * <p> + * NOTE: this is only used for backwards compatibility, bundles manifest using + * syntax version 2 will not recognize this header. + * + * <p>The attribute value may be retrieved from the + * <tt>Dictionary</tt> object returned by the <tt>Bundle.getHeaders</tt> method. + * @deprecated + */ + public final static String PROVIDE_PACKAGE = "Provide-Package"; //$NON-NLS-1$ + + /** + * Manifest header attribute (named "reprovide") + * for Require-Bundle + * identifying that any packages that are provided + * by the required bundle must be reprovided by the requiring bundle. + * The default value is <tt>false</tt>. + * <p> + * The attribute value is encoded in the Require-Bundle manifest + * header like: + * <pre> + * Require-Bundle: com.acme.module.test; reprovide="true" + * </pre> + * <p> + * NOTE: this is only used for backwards compatibility, bundles manifest using + * syntax version 2 will not recognize this attribute. + * @deprecated + */ + public final static String REPROVIDE_ATTRIBUTE = "reprovide"; //$NON-NLS-1$ + + /** + * Manifest header attribute (named "optional") + * for Require-Bundle + * identifying that a required bundle is optional and that + * the requiring bundle can be resolved if there is no + * suitable required bundle. + * The default value is <tt>false</tt>. + * + * <p>The attribute value is encoded in the Require-Bundle manifest + * header like: + * <pre> + * Require-Bundle: com.acme.module.test; optional="true" + * </pre> + * <p> + * NOTE: this is only used for backwards compatibility, bundles manifest using + * syntax version 2 will not recognize this attribute. + * @since 1.3 <b>EXPERIMENTAL</b> + * @deprecated + */ + public final static String OPTIONAL_ATTRIBUTE = "optional"; //$NON-NLS-1$ + + public static final String OSGI_RESOLVER_MODE = "osgi.resolverMode"; //$NON-NLS-1$ + public static final String STRICT_MODE = "strict"; //$NON-NLS-1$ + public static final String DEVELOPMENT_MODE = "development"; //$NON-NLS-1$ + + public static final String STATE_SYSTEM_BUNDLE = "osgi.system.bundle"; //$NON-NLS-1$ + + private static final String OSGI_OS = "osgi.os"; //$NON-NLS-1$ + private static final String OSGI_WS = "osgi.ws"; //$NON-NLS-1$ + private static final String OSGI_NL = "osgi.nl"; //$NON-NLS-1$ + private static final String OSGI_ARCH = "osgi.arch"; //$NON-NLS-1$ + public static final String[] PROPS = {OSGI_OS, OSGI_WS, OSGI_NL, OSGI_ARCH, Constants.FRAMEWORK_SYSTEMPACKAGES, Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, OSGI_RESOLVER_MODE, Constants.FRAMEWORK_EXECUTIONENVIRONMENT, "osgi.resolveOptional", "osgi.genericAliases", Constants.FRAMEWORK_OS_NAME, Constants.FRAMEWORK_OS_VERSION, Constants.FRAMEWORK_PROCESSOR, Constants.FRAMEWORK_LANGUAGE, STATE_SYSTEM_BUNDLE, Constants.FRAMEWORK_SYSTEMCAPABILITIES, Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA}; //$NON-NLS-1$ //$NON-NLS-2$ + private static final DisabledInfo[] EMPTY_DISABLEDINFOS = new DisabledInfo[0]; + public static final String OSGI_EE_NAMESPACE = "osgi.ee"; //$NON-NLS-1$ + + transient private Resolver resolver; + transient private StateDeltaImpl changes; + transient private boolean resolving = false; + transient private LinkedList<BundleDescription> removalPendings = new LinkedList<BundleDescription>(); + + private boolean resolved = true; + private long timeStamp = System.currentTimeMillis(); + private final KeyedHashSet bundleDescriptions = new KeyedHashSet(false); + private final Map<BundleDescription, List<ResolverError>> resolverErrors = new HashMap<BundleDescription, List<ResolverError>>(); + private StateObjectFactory factory; + private final KeyedHashSet resolvedBundles = new KeyedHashSet(); + private final Map<BundleDescription, List<DisabledInfo>> disabledBundles = new HashMap<BundleDescription, List<DisabledInfo>>(); + private boolean fullyLoaded = false; + private boolean dynamicCacheChanged = false; + // only used for lazy loading of BundleDescriptions + private StateReader reader; + @SuppressWarnings("unchecked") + private Dictionary<Object, Object>[] platformProperties = new Dictionary[] {new Hashtable<String, String>(PROPS.length)}; // Dictionary here because of Filter API + private long highestBundleId = -1; + private final Set<String> platformPropertyKeys = new HashSet<String>(PROPS.length); + private ResolverHookFactory hookFactory; + private ResolverHook hook; + private boolean developmentMode = false; + + private static long cumulativeTime; + + final Object monitor = new Object(); + + // to prevent extra-package instantiation + protected StateImpl() { + // always add the default platform property keys. + addPlatformPropertyKeys(PROPS); + } + + public boolean addBundle(BundleDescription description) { + synchronized (this.monitor) { + if (!basicAddBundle(description)) + return false; + String platformFilter = description.getPlatformFilter(); + if (platformFilter != null) { + try { + // add any new platform filter propery keys this bundle is using + FilterImpl filter = FilterImpl.newInstance(platformFilter); + addPlatformPropertyKeys(filter.getAttributes()); + } catch (InvalidSyntaxException e) { + // ignore this is handled in another place + } + } + NativeCodeSpecification nativeCode = description.getNativeCodeSpecification(); + if (nativeCode != null) { + NativeCodeDescription[] suppliers = nativeCode.getPossibleSuppliers(); + for (int i = 0; i < suppliers.length; i++) { + FilterImpl filter = (FilterImpl) suppliers[i].getFilter(); + if (filter != null) + addPlatformPropertyKeys(filter.getAttributes()); + } + } + resolved = false; + getDelta().recordBundleAdded((BundleDescriptionImpl) description); + if (getSystemBundle().equals(description.getSymbolicName())) + resetAllSystemCapabilities(); + if (resolver != null) + resolver.bundleAdded(description); + updateTimeStamp(); + return true; + } + } + + public boolean updateBundle(BundleDescription newDescription) { + synchronized (this.monitor) { + BundleDescriptionImpl existing = (BundleDescriptionImpl) bundleDescriptions.get((BundleDescriptionImpl) newDescription); + if (existing == null) + return false; + if (!bundleDescriptions.remove(existing)) + return false; + resolvedBundles.remove(existing); + List<DisabledInfo> infos = disabledBundles.remove(existing); + if (infos != null) { + List<DisabledInfo> newInfos = new ArrayList<DisabledInfo>(infos.size()); + for (Iterator<DisabledInfo> iInfos = infos.iterator(); iInfos.hasNext();) { + DisabledInfo info = iInfos.next(); + newInfos.add(new DisabledInfo(info.getPolicyName(), info.getMessage(), newDescription)); + } + disabledBundles.put(newDescription, newInfos); + } + existing.setStateBit(BundleDescriptionImpl.REMOVAL_PENDING, true); + if (!basicAddBundle(newDescription)) + return false; + resolved = false; + getDelta().recordBundleUpdated((BundleDescriptionImpl) newDescription); + if (getSystemBundle().equals(newDescription.getSymbolicName())) + resetAllSystemCapabilities(); + if (resolver != null) { + boolean pending = isInUse(existing); + resolver.bundleUpdated(newDescription, existing, pending); + if (pending) { + getDelta().recordBundleRemovalPending(existing); + addRemovalPending(existing); + } else { + // an existing bundle has been updated with no dependents it can safely be unresolved now + try { + resolving = true; + resolverErrors.remove(existing); + resolveBundle(existing, false, null, null, null, null, null, null, null, null); + } finally { + resolving = false; + } + } + } + updateTimeStamp(); + return true; + } + } + + public BundleDescription removeBundle(long bundleId) { + synchronized (this.monitor) { + BundleDescription toRemove = getBundle(bundleId); + if (toRemove == null || !removeBundle(toRemove)) + return null; + return toRemove; + } + } + + public boolean removeBundle(BundleDescription toRemove) { + synchronized (this.monitor) { + toRemove = (BundleDescription) bundleDescriptions.get((KeyedElement) toRemove); + if (toRemove == null || !bundleDescriptions.remove((KeyedElement) toRemove)) + return false; + resolvedBundles.remove((KeyedElement) toRemove); + disabledBundles.remove(toRemove); + resolved = false; + getDelta().recordBundleRemoved((BundleDescriptionImpl) toRemove); + ((BundleDescriptionImpl) toRemove).setStateBit(BundleDescriptionImpl.REMOVAL_PENDING, true); + if (resolver != null) { + boolean pending = isInUse(toRemove); + resolver.bundleRemoved(toRemove, pending); + if (pending) { + getDelta().recordBundleRemovalPending((BundleDescriptionImpl) toRemove); + addRemovalPending(toRemove); + } else { + // a bundle has been removed with no dependents it can safely be unresolved now + try { + resolving = true; + resolverErrors.remove(toRemove); + resolveBundle(toRemove, false, null, null, null, null, null); + } finally { + resolving = false; + } + } + } + updateTimeStamp(); + return true; + } + } + + private boolean isInUse(BundleDescription bundle) { + return bundle.getDependents().length > 0; + } + + public StateDelta getChanges() { + synchronized (this.monitor) { + return getDelta(); + } + } + + private StateDeltaImpl getDelta() { + if (changes == null) + changes = new StateDeltaImpl(this); + return changes; + } + + public BundleDescription[] getBundles(String symbolicName) { + synchronized (this.monitor) { + if (Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(symbolicName)) + symbolicName = getSystemBundle(); + final List<BundleDescription> bundles = new ArrayList<BundleDescription>(); + for (Iterator<KeyedElement> iter = bundleDescriptions.iterator(); iter.hasNext();) { + BundleDescription bundle = (BundleDescription) iter.next(); + if (symbolicName.equals(bundle.getSymbolicName())) + bundles.add(bundle); + } + return bundles.toArray(new BundleDescription[bundles.size()]); + } + } + + public BundleDescription[] getBundles() { + synchronized (this.monitor) { + return (BundleDescription[]) bundleDescriptions.elements(new BundleDescription[bundleDescriptions.size()]); + } + } + + public BundleDescription getBundle(long id) { + synchronized (this.monitor) { + BundleDescription result = (BundleDescription) bundleDescriptions.getByKey(new Long(id)); + if (result != null) + return result; + // need to look in removal pending bundles; + for (Iterator<BundleDescription> iter = removalPendings.iterator(); iter.hasNext();) { + BundleDescription removedBundle = iter.next(); + if (removedBundle.getBundleId() == id) // just return the first matching id + return removedBundle; + } + return null; + } + } + + public BundleDescription getBundle(String name, Version version) { + synchronized (this.monitor) { + BundleDescription[] allBundles = getBundles(name); + if (allBundles.length == 1) + return version == null || allBundles[0].getVersion().equals(version) ? allBundles[0] : null; + if (allBundles.length == 0) + return null; + BundleDescription unresolvedFound = null; + BundleDescription resolvedFound = null; + for (int i = 0; i < allBundles.length; i++) { + BundleDescription current = allBundles[i]; + BundleDescription base; + + if (current.isResolved()) + base = resolvedFound; + else + base = unresolvedFound; + + if (version == null || current.getVersion().equals(version)) { + if (base != null && (base.getVersion().compareTo(current.getVersion()) <= 0 || base.getBundleId() > current.getBundleId())) { + if (base == resolvedFound) + resolvedFound = current; + else + unresolvedFound = current; + } else { + if (current.isResolved()) + resolvedFound = current; + else + unresolvedFound = current; + } + + } + } + if (resolvedFound != null) + return resolvedFound; + return unresolvedFound; + } + } + + public long getTimeStamp() { + synchronized (this.monitor) { + return timeStamp; + } + } + + public boolean isResolved() { + synchronized (this.monitor) { + return resolved || isEmpty(); + } + } + + public void resolveConstraint(VersionConstraint constraint, BaseDescription supplier) { + ((VersionConstraintImpl) constraint).setSupplier(supplier); + } + + /** + * @deprecated + */ + public void resolveBundle(BundleDescription bundle, boolean status, BundleDescription[] hosts, ExportPackageDescription[] selectedExports, BundleDescription[] resolvedRequires, ExportPackageDescription[] resolvedImports) { + resolveBundle(bundle, status, hosts, selectedExports, null, resolvedRequires, resolvedImports); + } + + /** + * @deprecated + */ + public void resolveBundle(BundleDescription bundle, boolean status, BundleDescription[] hosts, ExportPackageDescription[] selectedExports, ExportPackageDescription[] substitutedExports, BundleDescription[] resolvedRequires, ExportPackageDescription[] resolvedImports) { + resolveBundle(bundle, status, hosts, selectedExports, substitutedExports, null, resolvedRequires, resolvedImports, null, null); + } + + public void resolveBundle(BundleDescription bundle, boolean status, BundleDescription[] hosts, ExportPackageDescription[] selectedExports, ExportPackageDescription[] substitutedExports, GenericDescription[] selectedCapabilities, BundleDescription[] resolvedRequires, ExportPackageDescription[] resolvedImports, GenericDescription[] resolvedCapabilities, Map<String, List<StateWire>> resolvedWires) { + synchronized (this.monitor) { + if (!resolving) + throw new IllegalStateException(); // TODO need error message here! + BundleDescriptionImpl modifiable = (BundleDescriptionImpl) bundle; + // must record the change before setting the resolve state to + // accurately record if a change has happened. + getDelta().recordBundleResolved(modifiable, status); + // force the new resolution data to stay in memory; we will not read this from disk anymore + modifiable.setLazyLoaded(false); + modifiable.setStateBit(BundleDescriptionImpl.RESOLVED, status); + if (status) { + resolverErrors.remove(modifiable); + resolvedBundles.add(modifiable); + } else { + // remove the bundle from the resolved pool + resolvedBundles.remove(modifiable); + modifiable.removeDependencies(); + } + // to support development mode we will resolveConstraints even if the resolve status == false + // we only do this if the resolved constraints are not null + if (selectedExports == null || resolvedRequires == null || resolvedImports == null) + unresolveConstraints(modifiable); + else + resolveConstraints(modifiable, hosts, selectedExports, substitutedExports, selectedCapabilities, resolvedRequires, resolvedImports, resolvedCapabilities, resolvedWires); + } + } + + public void removeBundleComplete(BundleDescription bundle) { + synchronized (this.monitor) { + if (!resolving) + throw new IllegalStateException(); // TODO need error message here! + getDelta().recordBundleRemovalComplete((BundleDescriptionImpl) bundle); + removalPendings.remove(bundle); + } + } + + private void resolveConstraints(BundleDescriptionImpl bundle, BundleDescription[] hosts, ExportPackageDescription[] selectedExports, ExportPackageDescription[] substitutedExports, GenericDescription[] selectedCapabilities, BundleDescription[] resolvedRequires, ExportPackageDescription[] resolvedImports, GenericDescription[] resolvedCapabilities, Map<String, List<StateWire>> resolvedWires) { + HostSpecificationImpl hostSpec = (HostSpecificationImpl) bundle.getHost(); + if (hostSpec != null) { + if (hosts != null) { + hostSpec.setHosts(hosts); + for (int i = 0; i < hosts.length; i++) { + ((BundleDescriptionImpl) hosts[i]).addDependency(bundle, true); + checkHostForSubstitutedExports((BundleDescriptionImpl) hosts[i], bundle); + } + } + } + + bundle.setSelectedExports(selectedExports); + bundle.setResolvedRequires(resolvedRequires); + bundle.setResolvedImports(resolvedImports); + bundle.setSubstitutedExports(substitutedExports); + bundle.setSelectedCapabilities(selectedCapabilities); + bundle.setResolvedCapabilities(resolvedCapabilities); + bundle.setStateWires(resolvedWires); + + bundle.addDependencies(hosts, true); + bundle.addDependencies(resolvedRequires, true); + bundle.addDependencies(resolvedImports, true); + bundle.addDependencies(resolvedCapabilities, true); + } + + private void checkHostForSubstitutedExports(BundleDescriptionImpl host, BundleDescriptionImpl fragment) { + // TODO need to handle this case where a fragment has its own export substituted + // there are issues here because the order in which fragments are resolved is not always the same ... + } + + private void unresolveConstraints(BundleDescriptionImpl bundle) { + HostSpecificationImpl host = (HostSpecificationImpl) bundle.getHost(); + if (host != null) + host.setHosts(null); + + bundle.setSelectedExports(null); + bundle.setResolvedImports(null); + bundle.setResolvedRequires(null); + bundle.setSubstitutedExports(null); + bundle.setSelectedCapabilities(null); + bundle.setResolvedCapabilities(null); + bundle.setStateWires(null); + bundle.clearAddedDynamicImportPackages(); + + // remove the constraint suppliers + NativeCodeSpecificationImpl nativeCode = (NativeCodeSpecificationImpl) bundle.getNativeCodeSpecification(); + if (nativeCode != null) + nativeCode.setSupplier(null); + ImportPackageSpecification[] imports = bundle.getImportPackages(); + for (int i = 0; i < imports.length; i++) + ((ImportPackageSpecificationImpl) imports[i]).setSupplier(null); + BundleSpecification[] requires = bundle.getRequiredBundles(); + for (int i = 0; i < requires.length; i++) + ((BundleSpecificationImpl) requires[i]).setSupplier(null); + GenericSpecification[] genericRequires = bundle.getGenericRequires(); + if (genericRequires.length > 0) + for (int i = 0; i < genericRequires.length; i++) + ((GenericSpecificationImpl) genericRequires[i]).setSupplers(null); + + bundle.removeDependencies(); + } + + private StateDelta resolve(boolean incremental, BundleDescription[] reResolve, BundleDescription[] triggers) { + fullyLoad(); + synchronized (this.monitor) { + if (resolver == null) + throw new IllegalStateException("no resolver set"); //$NON-NLS-1$ + if (resolving == true) + throw new IllegalStateException("An attempt to start a nested resolve process has been detected."); //$NON-NLS-1$ + try { + resolving = true; + long start = 0; + if (!incremental) { + resolved = false; + reResolve = getBundles(); + // need to get any removal pendings before flushing + if (removalPendings.size() > 0) { + BundleDescription[] removed = internalGetRemovalPending(); + reResolve = mergeBundles(reResolve, removed); + } + flush(reResolve); + } else { + if (resolved && reResolve == null) + return new StateDeltaImpl(this); + if (developmentMode) { + // in dev mode we need to aggressively flush removal pendings + if (removalPendings.size() > 0) { + BundleDescription[] removed = internalGetRemovalPending(); + reResolve = mergeBundles(reResolve, removed); + } + } + if (reResolve == null) + reResolve = internalGetRemovalPending(); + if (triggers == null) { + Set<BundleDescription> triggerSet = new HashSet<BundleDescription>(); + Collection<BundleDescription> closure = getDependencyClosure(Arrays.asList(reResolve)); + for (BundleDescription toRefresh : closure) { + Bundle bundle = toRefresh.getBundle(); + if (bundle != null && (bundle.getState() & (Bundle.INSTALLED | Bundle.UNINSTALLED | Bundle.RESOLVED)) == 0) + triggerSet.add(toRefresh); + } + triggers = triggerSet.toArray(new BundleDescription[triggerSet.size()]); + } + } + // use the Headers class to handle ignoring case while matching keys (bug 180817) + @SuppressWarnings("unchecked") + Headers<Object, Object>[] tmpPlatformProperties = new Headers[platformProperties.length]; + for (int i = 0; i < platformProperties.length; i++) { + tmpPlatformProperties[i] = new Headers<Object, Object>(platformProperties[i].size()); + for (Enumeration<Object> keys = platformProperties[i].keys(); keys.hasMoreElements();) { + Object key = keys.nextElement(); + tmpPlatformProperties[i].put(key, platformProperties[i].get(key)); + } + } + + ResolverHookFactory currentFactory = hookFactory; + if (currentFactory != null) { + @SuppressWarnings("unchecked") + Collection<BundleRevision> triggerRevisions = Collections.unmodifiableCollection(triggers == null ? Collections.EMPTY_LIST : Arrays.asList((BundleRevision[]) triggers)); + begin(triggerRevisions); + } + ResolverHookException error = null; + try { + resolver.resolve(reResolve, tmpPlatformProperties); + } catch (ResolverHookException e) { + error = e; + resolverErrors.clear(); + } + resolved = removalPendings.size() == 0; + + StateDeltaImpl savedChanges = changes == null ? new StateDeltaImpl(this) : changes; + savedChanges.setResolverHookException(error); + changes = new StateDeltaImpl(this); + + if (savedChanges.getChanges().length > 0) + updateTimeStamp(); + return savedChanges; + } finally { + resolving = false; + } + } + } + + private BundleDescription[] mergeBundles(BundleDescription[] reResolve, BundleDescription[] removed) { + if (reResolve == null) + return removed; // just return all the removed bundles + if (reResolve.length == 0) + return reResolve; // if reResolve length==0 then we want to prevent pending removal + // merge in all removal pending bundles that are not already in the list + List<BundleDescription> result = new ArrayList<BundleDescription>(reResolve.length + removed.length); + for (int i = 0; i < reResolve.length; i++) + result.add(reResolve[i]); + for (int i = 0; i < removed.length; i++) { + boolean found = false; + for (int j = 0; j < reResolve.length; j++) { + if (removed[i] == reResolve[j]) { + found = true; + break; + } + } + if (!found) + result.add(removed[i]); + } + return result.toArray(new BundleDescription[result.size()]); + } + + private void flush(BundleDescription[] bundles) { + resolver.flush(); + resolved = false; + resolverErrors.clear(); + if (resolvedBundles.isEmpty()) + return; + for (int i = 0; i < bundles.length; i++) { + resolveBundle(bundles[i], false, null, null, null, null, null); + } + resolvedBundles.clear(); + } + + public StateDelta resolve() { + return resolve(true, null, null); + } + + public StateDelta resolve(boolean incremental) { + return resolve(incremental, null, null); + } + + public StateDelta resolve(BundleDescription[] reResolve) { + return resolve(true, reResolve, null); + } + + public StateDelta resolve(BundleDescription[] resolve, boolean discard) { + BundleDescription[] reResolve = discard ? resolve : new BundleDescription[0]; + BundleDescription[] triggers = discard ? null : resolve; + return resolve(true, reResolve, triggers); + } + + @SuppressWarnings("deprecation") + public void setOverrides(Object value) { + throw new UnsupportedOperationException(); + } + + public void setResolverHookFactory(ResolverHookFactory hookFactory) { + synchronized (this.monitor) { + if (this.hookFactory != null) + throw new IllegalStateException("Resolver hook factory is already set."); //$NON-NLS-1$ + this.hookFactory = hookFactory; + } + } + + private ResolverHook begin(Collection<BundleRevision> triggers) { + ResolverHookFactory current; + synchronized (this.monitor) { + current = this.hookFactory; + } + ResolverHook newHook = current.begin(triggers); + synchronized (this.monitor) { + this.hook = newHook; + } + return newHook; + } + + ResolverHookFactory getResolverHookFactory() { + synchronized (this.monitor) { + return this.hookFactory; + } + } + + public ResolverHook getResolverHook() { + synchronized (this.monitor) { + return this.hook; + } + } + + public BundleDescription[] getResolvedBundles() { + synchronized (this.monitor) { + return (BundleDescription[]) resolvedBundles.elements(new BundleDescription[resolvedBundles.size()]); + } + } + + public boolean isEmpty() { + synchronized (this.monitor) { + return bundleDescriptions.isEmpty(); + } + } + + void setResolved(boolean resolved) { + synchronized (this.monitor) { + this.resolved = resolved; + } + } + + boolean basicAddBundle(BundleDescription description) { + synchronized (this.monitor) { + StateImpl origState = (StateImpl) description.getContainingState(); + if (origState != null && origState != this) { + if (origState.removalPendings.contains(description)) + throw new IllegalStateException(NLS.bind(StateMsg.BUNDLE_PENDING_REMOVE_STATE, description.toString())); + if (origState.getBundle(description.getBundleId()) == description) + throw new IllegalStateException(NLS.bind(StateMsg.BUNDLE_IN_OTHER_STATE, description.toString())); + } + ((BundleDescriptionImpl) description).setContainingState(this); + ((BundleDescriptionImpl) description).setStateBit(BundleDescriptionImpl.REMOVAL_PENDING, false); + if (bundleDescriptions.add((BundleDescriptionImpl) description)) { + if (description.getBundleId() > getHighestBundleId()) + highestBundleId = description.getBundleId(); + return true; + } + return false; + } + } + + void addResolvedBundle(BundleDescriptionImpl resolvedBundle) { + synchronized (this.monitor) { + resolvedBundles.add(resolvedBundle); + } + } + + public ExportPackageDescription[] getExportedPackages() { + fullyLoad(); + synchronized (this.monitor) { + List<ExportPackageDescription> allExportedPackages = new ArrayList<ExportPackageDescription>(); + for (Iterator<KeyedElement> iter = resolvedBundles.iterator(); iter.hasNext();) { + BundleDescription bundle = (BundleDescription) iter.next(); + ExportPackageDescription[] bundlePackages = bundle.getSelectedExports(); + if (bundlePackages == null) + continue; + for (int i = 0; i < bundlePackages.length; i++) + allExportedPackages.add(bundlePackages[i]); + } + for (Iterator<BundleDescription> iter = removalPendings.iterator(); iter.hasNext();) { + BundleDescription bundle = iter.next(); + ExportPackageDescription[] bundlePackages = bundle.getSelectedExports(); + if (bundlePackages == null) + continue; + for (int i = 0; i < bundlePackages.length; i++) + allExportedPackages.add(bundlePackages[i]); + } + return allExportedPackages.toArray(new ExportPackageDescription[allExportedPackages.size()]); + } + } + + BundleDescription[] getFragments(final BundleDescription host) { + final List<BundleDescription> fragments = new ArrayList<BundleDescription>(); + for (Iterator<KeyedElement> iter = bundleDescriptions.iterator(); iter.hasNext();) { + BundleDescription bundle = (BundleDescription) iter.next(); + HostSpecification hostSpec = bundle.getHost(); + + if (hostSpec != null) { + BundleDescription[] hosts = hostSpec.getHosts(); + if (hosts != null) + for (int i = 0; i < hosts.length; i++) + if (hosts[i] == host) { + fragments.add(bundle); + break; + } + } + } + return fragments.toArray(new BundleDescription[fragments.size()]); + } + + public void setTimeStamp(long newTimeStamp) { + synchronized (this.monitor) { + timeStamp = newTimeStamp; + } + } + + private void updateTimeStamp() { + synchronized (this.monitor) { + if (getTimeStamp() == Long.MAX_VALUE) + setTimeStamp(0); + setTimeStamp(getTimeStamp() + 1); + } + } + + public StateObjectFactory getFactory() { + return factory; + } + + void setFactory(StateObjectFactory factory) { + this.factory = factory; + } + + public BundleDescription getBundleByLocation(String location) { + synchronized (this.monitor) { + for (Iterator<KeyedElement> i = bundleDescriptions.iterator(); i.hasNext();) { + BundleDescription current = (BundleDescription) i.next(); + if (location.equals(current.getLocation())) + return current; + } + return null; + } + } + + public Resolver getResolver() { + synchronized (this.monitor) { + return resolver; + } + } + + public void setResolver(Resolver newResolver) { + if (resolver == newResolver) + return; + if (resolver != null) { + Resolver oldResolver = resolver; + resolver = null; + oldResolver.setState(null); + } + synchronized (this.monitor) { + resolver = newResolver; + } + if (resolver == null) + return; + resolver.setState(this); + } + + public boolean setPlatformProperties(Dictionary<?, ?> platformProperties) { + return setPlatformProperties(new Dictionary[] {platformProperties}); + } + + public boolean setPlatformProperties(Dictionary<?, ?>[] platformProperties) { + return setPlatformProperties(platformProperties, true); + } + + synchronized boolean setPlatformProperties(Dictionary<?, ?>[] platformProperties, boolean resetSystemExports) { + if (platformProperties.length == 0) + throw new IllegalArgumentException(); + // copy the properties for our use internally; + // only copy String and String[] values + @SuppressWarnings("unchecked") + Dictionary<Object, Object>[] newPlatformProperties = new Dictionary[platformProperties.length]; + for (int i = 0; i < platformProperties.length; i++) { + newPlatformProperties[i] = new Hashtable<Object, Object>(platformProperties[i].size()); + synchronized (platformProperties[i]) { + for (Enumeration<?> keys = platformProperties[i].keys(); keys.hasMoreElements();) { + Object key = keys.nextElement(); + Object value = platformProperties[i].get(key); + newPlatformProperties[i].put(key, value); + } + } + // make sure the bundle native code osgi properties have decent defaults + if (newPlatformProperties[i].get(Constants.FRAMEWORK_OS_NAME) == null && newPlatformProperties[i].get(OSGI_OS) != null) + newPlatformProperties[i].put(Constants.FRAMEWORK_OS_NAME, newPlatformProperties[i].get(OSGI_OS)); + if (newPlatformProperties[i].get(Constants.FRAMEWORK_PROCESSOR) == null && newPlatformProperties[i].get(OSGI_ARCH) != null) + newPlatformProperties[i].put(Constants.FRAMEWORK_PROCESSOR, newPlatformProperties[i].get(OSGI_ARCH)); + if (newPlatformProperties[i].get(Constants.FRAMEWORK_LANGUAGE) == null && newPlatformProperties[i].get(OSGI_NL) instanceof String) { + String osgiNL = (String) newPlatformProperties[i].get(OSGI_NL); + int idx = osgiNL.indexOf('_'); + if (idx >= 0) + osgiNL = osgiNL.substring(0, idx); + newPlatformProperties[i].put(Constants.FRAMEWORK_LANGUAGE, osgiNL); + } + + } + boolean result = false; + boolean performResetSystemExports = false; + boolean performResetSystemCapabilities = false; + if (this.platformProperties.length != newPlatformProperties.length) { + result = true; + performResetSystemExports = true; + performResetSystemCapabilities = true; + } else { + // we need to see if any of the existing filter prop keys have changed + String[] keys = getPlatformPropertyKeys(); + for (int i = 0; i < newPlatformProperties.length && !result; i++) { + result |= changedProps(this.platformProperties[i], newPlatformProperties[i], keys); + if (resetSystemExports) { + performResetSystemExports |= checkProp(this.platformProperties[i].get(Constants.FRAMEWORK_SYSTEMPACKAGES), newPlatformProperties[i].get(Constants.FRAMEWORK_SYSTEMPACKAGES)); + performResetSystemExports |= checkProp(this.platformProperties[i].get(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA), newPlatformProperties[i].get(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA)); + performResetSystemExports |= checkProp(this.platformProperties[i].get(Constants.SYSTEM_BUNDLE_SYMBOLICNAME), newPlatformProperties[i].get(Constants.SYSTEM_BUNDLE_SYMBOLICNAME)); + performResetSystemCapabilities |= checkProp(this.platformProperties[i].get(Constants.FRAMEWORK_SYSTEMCAPABILITIES), newPlatformProperties[i].get(Constants.FRAMEWORK_SYSTEMCAPABILITIES)); + performResetSystemCapabilities |= checkProp(this.platformProperties[i].get(Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA), newPlatformProperties[i].get(Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA)); + performResetSystemCapabilities |= checkProp(this.platformProperties[i].get(Constants.FRAMEWORK_EXECUTIONENVIRONMENT), newPlatformProperties[i].get(Constants.FRAMEWORK_EXECUTIONENVIRONMENT)); + } + } + } + // always do a complete replacement of the properties in case new bundles are added that uses new filter props + this.platformProperties = newPlatformProperties; + if (performResetSystemExports) + resetSystemExports(); + if (performResetSystemCapabilities) + resetSystemCapabilities(); + developmentMode = this.platformProperties.length == 0 ? false : DEVELOPMENT_MODE.equals(this.platformProperties[0].get(OSGI_RESOLVER_MODE)); + return result; + } + + private void resetAllSystemCapabilities() { + resetSystemExports(); + resetSystemCapabilities(); + } + + private void resetSystemExports() { + BundleDescription[] systemBundles = getBundles(Constants.SYSTEM_BUNDLE_SYMBOLICNAME); + for (int idx = 0; idx < systemBundles.length; idx++) { + BundleDescriptionImpl systemBundle = (BundleDescriptionImpl) systemBundles[idx]; + ExportPackageDescription[] exports = systemBundle.getExportPackages(); + List<ExportPackageDescription> newExports = new ArrayList<ExportPackageDescription>(exports.length); + for (int i = 0; i < exports.length; i++) + if (((Integer) exports[i].getDirective(ExportPackageDescriptionImpl.EQUINOX_EE)).intValue() < 0) + newExports.add(exports[i]); + addSystemExports(newExports); + systemBundle.setExportPackages(newExports.toArray(new ExportPackageDescription[newExports.size()])); + } + } + + private void addSystemExports(List<ExportPackageDescription> exports) { + for (int i = 0; i < platformProperties.length; i++) + try { + addSystemExports(exports, ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, (String) platformProperties[i].get(Constants.FRAMEWORK_SYSTEMPACKAGES)), i); + addSystemExports(exports, ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, (String) platformProperties[i].get(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA)), i); + } catch (BundleException e) { + // TODO consider throwing this... + } + } + + private void addSystemExports(List<ExportPackageDescription> exports, ManifestElement[] elements, int index) { + if (elements == null) + return; + ExportPackageDescription[] systemExports = StateBuilder.createExportPackages(elements, null, null, false); + Integer profInx = new Integer(index); + for (int j = 0; j < systemExports.length; j++) { + ((ExportPackageDescriptionImpl) systemExports[j]).setDirective(ExportPackageDescriptionImpl.EQUINOX_EE, profInx); + exports.add(systemExports[j]); + } + } + + private void resetSystemCapabilities() { + BundleDescription[] systemBundles = getBundles(Constants.SYSTEM_BUNDLE_SYMBOLICNAME); + for (BundleDescription systemBundle : systemBundles) { + GenericDescription[] capabilities = systemBundle.getGenericCapabilities(); + List<GenericDescription> newCapabilities = new ArrayList<GenericDescription>(capabilities.length); + for (GenericDescription capability : capabilities) { + Object equinoxEEIndex = capability.getDeclaredAttributes().get(ExportPackageDescriptionImpl.EQUINOX_EE); + if (equinoxEEIndex == null) + newCapabilities.add(capability); // keep the built in ones. + } + // now add the externally defined ones + addSystemCapabilities(newCapabilities); + ((BundleDescriptionImpl) systemBundle).setGenericCapabilities(newCapabilities.toArray(new GenericDescription[newCapabilities.size()])); + } + } + + private void addSystemCapabilities(List<GenericDescription> capabilities) { + for (int i = 0; i < platformProperties.length; i++) + try { + addSystemCapabilities(capabilities, ManifestElement.parseHeader(Constants.PROVIDE_CAPABILITY, (String) platformProperties[i].get(Constants.FRAMEWORK_SYSTEMCAPABILITIES)), i); + addSystemCapabilities(capabilities, ManifestElement.parseHeader(Constants.PROVIDE_CAPABILITY, (String) platformProperties[i].get(Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA)), i); + checkOSGiEE(capabilities, (String) platformProperties[i].get(Constants.FRAMEWORK_EXECUTIONENVIRONMENT), i); + } catch (BundleException e) { + // TODO consider throwing this... + } + } + + private void checkOSGiEE(List<GenericDescription> capabilities, String profileEE, Integer profileIndex) { + if (profileEE == null || profileEE.length() == 0) + return; + for (GenericDescription capability : capabilities) { + if (OSGI_EE_NAMESPACE.equals(capability.getType()) && profileIndex.equals(capability.getAttributes().get(ExportPackageDescriptionImpl.EQUINOX_EE))) + return; // profile already specifies osgi.ee capabilities + } + Map<String, List<String>> eeVersions = new HashMap<String, List<String>>(); + String[] ees = ManifestElement.getArrayFromList(profileEE); + for (String ee : ees) { + String[] eeNameVersion = StateBuilder.getOSGiEENameVersion(ee); + + List<String> versions = eeVersions.get(eeNameVersion[0]); + if (versions == null) { + versions = new ArrayList<String>(); + eeVersions.put(eeNameVersion[0], versions); + } + if (eeNameVersion[1] != null && !versions.contains(eeNameVersion[1])) + versions.add(eeNameVersion[1]); + } + for (Map.Entry<String, List<String>> eeVersion : eeVersions.entrySet()) { + GenericDescriptionImpl capability = new GenericDescriptionImpl(); + capability.setType(OSGI_EE_NAMESPACE); + Dictionary<String, Object> attributes = new Hashtable<String, Object>(); + attributes.put(capability.getType(), eeVersion.getKey()); + if (eeVersion.getValue().size() > 0) { + List<Version> versions = new ArrayList<Version>(eeVersion.getValue().size()); + for (String version : eeVersion.getValue()) { + versions.add(new Version(version)); + } + attributes.put("version", versions); //$NON-NLS-1$ + } + attributes.put(ExportPackageDescriptionImpl.EQUINOX_EE, profileIndex); + capability.setAttributes(attributes); + capabilities.add(capability); + } + } + + private void addSystemCapabilities(List<GenericDescription> capabilities, ManifestElement[] elements, Integer profileIndex) { + try { + StateBuilder.createOSGiCapabilities(elements, capabilities, profileIndex); + } catch (BundleException e) { + throw new RuntimeException("Unexpected exception adding system capabilities.", e); //$NON-NLS-1$ + } + } + + @SuppressWarnings("rawtypes") + public Dictionary[] getPlatformProperties() { + return platformProperties; + } + + private boolean checkProp(Object origObj, Object newObj) { + if ((origObj == null && newObj != null) || (origObj != null && newObj == null)) + return true; + if (origObj == null) + return false; + if (origObj.getClass() != newObj.getClass()) + return true; + if (origObj instanceof String[]) { + String[] origProps = (String[]) origObj; + String[] newProps = (String[]) newObj; + if (origProps.length != newProps.length) + return true; + for (int i = 0; i < origProps.length; i++) { + if (!origProps[i].equals(newProps[i])) + return true; + } + return false; + } + return !origObj.equals(newObj); + } + + private boolean changedProps(Dictionary<Object, Object> origProps, Dictionary<Object, Object> newProps, String[] keys) { + for (int i = 0; i < keys.length; i++) { + Object origProp = origProps.get(keys[i]); + Object newProp = newProps.get(keys[i]); + if (checkProp(origProp, newProp)) + return true; + } + return false; + } + + public String getSystemBundle() { + String symbolicName = null; + if (platformProperties != null && platformProperties.length > 0) + symbolicName = (String) platformProperties[0].get(STATE_SYSTEM_BUNDLE); + return symbolicName != null ? symbolicName : EquinoxContainer.NAME; + } + + public BundleDescription[] getRemovalPending() { + synchronized (this.monitor) { + return removalPendings.toArray(new BundleDescription[removalPendings.size()]); + } + } + + private void addRemovalPending(BundleDescription removed) { + synchronized (this.monitor) { + if (!removalPendings.contains(removed)) + removalPendings.addFirst(removed); + } + } + + public Collection<BundleDescription> getDependencyClosure(Collection<BundleDescription> bundles) { + BundleDescription[] removals = getRemovalPending(); + Set<BundleDescription> result = new HashSet<BundleDescription>(); + for (BundleDescription bundle : bundles) { + addDependents(bundle, result, removals); + } + return result; + } + + private static void addDependents(BundleDescription bundle, Set<BundleDescription> result, BundleDescription[] removals) { + if (result.contains(bundle)) + return; // avoid cycles + result.add(bundle); + BundleDescription[] dependents = bundle.getDependents(); + for (BundleDescription dependent : dependents) + addDependents(dependent, result, removals); + // check if this is a removal pending + for (BundleDescription removed : removals) { + if (removed.getBundleId() == bundle.getBundleId()) + addDependents(removed, result, removals); + } + } + + /** + * Returns the latest versions BundleDescriptions which have old removal pending versions. + * @return the BundleDescriptions that have removal pending versions. + */ + private BundleDescription[] internalGetRemovalPending() { + synchronized (this.monitor) { + Iterator<BundleDescription> removed = removalPendings.iterator(); + BundleDescription[] result = new BundleDescription[removalPendings.size()]; + int i = 0; + while (removed.hasNext()) + // we return the latest version of the description if it is still contained in the state (bug 287636) + result[i++] = getBundle(removed.next().getBundleId()); + return result; + } + } + + public ExportPackageDescription linkDynamicImport(BundleDescription importingBundle, String requestedPackage) { + if (resolver == null) + throw new IllegalStateException("no resolver set"); //$NON-NLS-1$ + BundleDescriptionImpl importer = (BundleDescriptionImpl) importingBundle; + if (importer.getDynamicStamp(requestedPackage) == getTimeStamp()) + return null; + fullyLoad(); + synchronized (this.monitor) { + ResolverHook currentHook = null; + try { + resolving = true; + ResolverHookFactory currentFactory = hookFactory; + if (currentFactory != null) { + Collection<BundleRevision> triggers = new ArrayList<BundleRevision>(1); + triggers.add(importingBundle); + triggers = Collections.unmodifiableCollection(triggers); + currentHook = begin(triggers); + } + // ask the resolver to resolve our dynamic import + ExportPackageDescriptionImpl result = (ExportPackageDescriptionImpl) resolver.resolveDynamicImport(importingBundle, requestedPackage); + if (result == null) + importer.setDynamicStamp(requestedPackage, new Long(getTimeStamp())); + else { + importer.setDynamicStamp(requestedPackage, null); // remove any cached timestamp + // need to add the result to the list of resolved imports + importer.addDynamicResolvedImport(result); + } + setDynamicCacheChanged(true); + return result; + } finally { + resolving = false; + if (currentHook != null) + currentHook.end(); + } + } + + } + + public void addDynamicImportPackages(BundleDescription importingBundle, ImportPackageSpecification[] dynamicImports) { + synchronized (this.monitor) { + ((BundleDescriptionImpl) importingBundle).addDynamicImportPackages(dynamicImports); + setDynamicCacheChanged(true); + } + } + + void setReader(StateReader reader) { + synchronized (this.monitor) { + this.reader = reader; + } + } + + StateReader getReader() { + synchronized (this.monitor) { + return reader; + } + } + + // not synchronized on this to prevent deadlock + public final void fullyLoad() { + synchronized (this.monitor) { + if (reader == null) + return; + if (fullyLoaded == true) + return; + if (reader.isLazyLoaded()) + reader.fullyLoad(); + fullyLoaded = true; + } + } + + // not synchronized on this to prevent deadlock + public final boolean unloadLazyData(long checkStamp) { + // make sure no other thread is trying to unload or load + synchronized (this.monitor) { + if (checkStamp != getTimeStamp() || dynamicCacheChanged()) + return false; + if (reader.getAccessedFlag()) { + reader.setAccessedFlag(false); // reset accessed flag + return true; + } + fullyLoaded = false; + BundleDescription[] bundles = getBundles(); + for (int i = 0; i < bundles.length; i++) + ((BundleDescriptionImpl) bundles[i]).unload(); + reader.flushLazyObjectCache(); + resolver.flush(); + return true; + } + } + + public ExportPackageDescription[] getSystemPackages() { + synchronized (this.monitor) { + List<ExportPackageDescription> result = new ArrayList<ExportPackageDescription>(); + BundleDescription[] systemBundles = getBundles(Constants.SYSTEM_BUNDLE_SYMBOLICNAME); + if (systemBundles.length > 0) { + BundleDescriptionImpl systemBundle = (BundleDescriptionImpl) systemBundles[0]; + ExportPackageDescription[] exports = systemBundle.getExportPackages(); + for (int i = 0; i < exports.length; i++) + if (((Integer) exports[i].getDirective(ExportPackageDescriptionImpl.EQUINOX_EE)).intValue() >= 0) + result.add(exports[i]); + } + return result.toArray(new ExportPackageDescription[result.size()]); + } + } + + boolean inStrictMode() { + synchronized (this.monitor) { + return STRICT_MODE.equals(getPlatformProperties()[0].get(OSGI_RESOLVER_MODE)); + } + } + + public ResolverError[] getResolverErrors(BundleDescription bundle) { + synchronized (this.monitor) { + if (bundle.isResolved()) + return new ResolverError[0]; + List<ResolverError> result = resolverErrors.get(bundle); + return result == null ? new ResolverError[0] : result.toArray(new ResolverError[result.size()]); + } + } + + public void addResolverError(BundleDescription bundle, int type, String data, VersionConstraint unsatisfied) { + synchronized (this.monitor) { + if (!resolving) + throw new IllegalStateException(); // TODO need error message here! + List<ResolverError> errors = resolverErrors.get(bundle); + if (errors == null) { + errors = new ArrayList<ResolverError>(1); + resolverErrors.put(bundle, errors); + } + errors.add(new ResolverErrorImpl((BundleDescriptionImpl) bundle, type, data, unsatisfied)); + } + } + + public void removeResolverErrors(BundleDescription bundle) { + synchronized (this.monitor) { + if (!resolving) + throw new IllegalStateException(); // TODO need error message here! + resolverErrors.remove(bundle); + } + } + + public boolean dynamicCacheChanged() { + synchronized (this.monitor) { + return dynamicCacheChanged; + } + } + + void setDynamicCacheChanged(boolean dynamicCacheChanged) { + synchronized (this.monitor) { + this.dynamicCacheChanged = dynamicCacheChanged; + } + } + + public StateHelper getStateHelper() { + return StateHelperImpl.getInstance(); + } + + void addPlatformPropertyKeys(String[] keys) { + synchronized (platformPropertyKeys) { + for (int i = 0; i < keys.length; i++) + if (!platformPropertyKeys.contains(keys[i])) + platformPropertyKeys.add(keys[i]); + } + } + + String[] getPlatformPropertyKeys() { + synchronized (platformPropertyKeys) { + return platformPropertyKeys.toArray(new String[platformPropertyKeys.size()]); + } + } + + public long getHighestBundleId() { + synchronized (this.monitor) { + return highestBundleId; + } + } + + public void setNativePathsInvalid(NativeCodeDescription nativeCodeDescription, boolean hasInvalidNativePaths) { + ((NativeCodeDescriptionImpl) nativeCodeDescription).setInvalidNativePaths(hasInvalidNativePaths); + } + + public BundleDescription[] getDisabledBundles() { + synchronized (this.monitor) { + return disabledBundles.keySet().toArray(new BundleDescription[0]); + } + } + + public void addDisabledInfo(DisabledInfo disabledInfo) { + synchronized (this.monitor) { + if (getBundle(disabledInfo.getBundle().getBundleId()) != disabledInfo.getBundle()) + throw new IllegalArgumentException(NLS.bind(StateMsg.BUNDLE_NOT_IN_STATE, disabledInfo.getBundle())); + List<DisabledInfo> currentInfos = disabledBundles.get(disabledInfo.getBundle()); + if (currentInfos == null) { + currentInfos = new ArrayList<DisabledInfo>(1); + currentInfos.add(disabledInfo); + disabledBundles.put(disabledInfo.getBundle(), currentInfos); + } else { + Iterator<DisabledInfo> it = currentInfos.iterator(); + while (it.hasNext()) { + DisabledInfo currentInfo = it.next(); + if (disabledInfo.getPolicyName().equals(currentInfo.getPolicyName())) { + currentInfos.remove(currentInfo); + break; + } + } + currentInfos.add(disabledInfo); + } + updateTimeStamp(); + } + } + + public void removeDisabledInfo(DisabledInfo disabledInfo) { + synchronized (this.monitor) { + List<DisabledInfo> currentInfos = disabledBundles.get(disabledInfo.getBundle()); + if ((currentInfos != null) && currentInfos.contains(disabledInfo)) { + currentInfos.remove(disabledInfo); + if (currentInfos.isEmpty()) { + disabledBundles.remove(disabledInfo.getBundle()); + } + } + updateTimeStamp(); + } + } + + public DisabledInfo getDisabledInfo(BundleDescription bundle, String policyName) { + synchronized (this.monitor) { + List<DisabledInfo> currentInfos = disabledBundles.get(bundle); + if (currentInfos == null) + return null; + Iterator<DisabledInfo> it = currentInfos.iterator(); + while (it.hasNext()) { + DisabledInfo currentInfo = it.next(); + if (currentInfo.getPolicyName().equals(policyName)) { + return currentInfo; + } + } + return null; + } + } + + public DisabledInfo[] getDisabledInfos(BundleDescription bundle) { + synchronized (this.monitor) { + List<DisabledInfo> currentInfos = disabledBundles.get(bundle); + return currentInfos == null ? EMPTY_DISABLEDINFOS : currentInfos.toArray(new DisabledInfo[currentInfos.size()]); + } + } + + /* + * Used by StateWriter to get all the DisabledInfo objects to persist + */ + DisabledInfo[] getDisabledInfos() { + List<DisabledInfo> results = new ArrayList<DisabledInfo>(); + synchronized (this.monitor) { + for (Iterator<List<DisabledInfo>> allDisabledInfos = disabledBundles.values().iterator(); allDisabledInfos.hasNext();) + results.addAll(allDisabledInfos.next()); + } + return results.toArray(new DisabledInfo[results.size()]); + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateMessages.properties b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateMessages.properties new file mode 100644 index 000000000..acf9ffad8 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateMessages.properties @@ -0,0 +1,35 @@ +############################################################################### +# Copyright (c) 2004, 2011 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 +############################################################################### + +#State/Resolver Messages for EN locale +BUNDLE_NOT_IN_STATE=The bundle is not in the state: {0} +BUNDLE_IN_OTHER_STATE=The bundle belongs to another state: {0} +BUNDLE_PENDING_REMOVE_STATE = The bundle is pending remove in another state: {0} + +HEADER_REQUIRED=The \"{0}\" header must be specified +HEADER_PACKAGE_DUPLICATES=Cannot import a package more than once \"{0}\" +HEADER_PACKAGE_JAVA=Cannot specify java.* packages in Import/Export headers \"{0}\" +HEADER_VERSION_ERROR=The attributes \"{0}\" and \"{1}\" must match +HEADER_EXPORT_ATTR_ERROR=Specifying \"{0}\" in the \"{1}\" header is not permitted +HEADER_DIRECTIVE_DUPLICATES=Duplicate directives are not permitted \"{0}\" +HEADER_ATTRIBUTE_DUPLICATES=Duplicate attributes are not permitted \"{0}\" +HEADER_EXTENSION_ERROR=Extension bundle is not a fragment to the system bundle \"{0}\" + +RES_ERROR_DISABLEDBUNDLE=The bundle is disabled: {0} +RES_ERROR_MISSING_PERMISSION=Missing Permission: {0} +RES_ERROR_MISSING_CONSTRAINT=Missing Constraint: {0} +RES_ERROR_FRAGMENT_CONFLICT=Constraints from the fragment conflict with the host: {0} +RES_ERROR_USES_CONFLICT=Package uses conflict: {0} +RES_ERROR_SINGLETON_CONFLICT=Another singleton version selected: {0} +RES_ERROR_PLATFORM_FILTER=Platform filter did not match: {0} +RES_ERROR_NO_NATIVECODE_MATCH=No match found for native code: {0} +RES_ERROR_NATIVECODE_PATH_INVALID=The native code paths cannot be found: {0} +RES_ERROR_UNKNOWN=Unknown resolution error diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateMsg.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateMsg.java new file mode 100644 index 000000000..1aeb570ae --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateMsg.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2004, 2011 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.resolver; + +import org.eclipse.osgi.util.NLS; + +public class StateMsg extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.osgi.internal.resolver.StateMessages"; //$NON-NLS-1$ + + public static String BUNDLE_NOT_IN_STATE; + public static String BUNDLE_IN_OTHER_STATE; + public static String BUNDLE_PENDING_REMOVE_STATE; + + public static String HEADER_REQUIRED; + public static String HEADER_PACKAGE_DUPLICATES; + public static String HEADER_PACKAGE_JAVA; + public static String HEADER_VERSION_ERROR; + public static String HEADER_EXPORT_ATTR_ERROR; + public static String HEADER_DIRECTIVE_DUPLICATES; + public static String HEADER_ATTRIBUTE_DUPLICATES; + public static String HEADER_EXTENSION_ERROR; + + public static String RES_ERROR_DISABLEDBUNDLE; + public static String RES_ERROR_MISSING_PERMISSION; + public static String RES_ERROR_MISSING_CONSTRAINT; + public static String RES_ERROR_FRAGMENT_CONFLICT; + public static String RES_ERROR_USES_CONFLICT; + public static String RES_ERROR_SINGLETON_CONFLICT; + public static String RES_ERROR_PLATFORM_FILTER; + public static String RES_ERROR_NO_NATIVECODE_MATCH; + public static String RES_ERROR_NATIVECODE_PATH_INVALID; + public static String RES_ERROR_UNKNOWN; + + static { + // initialize resource bundles + NLS.initializeMessages(BUNDLE_NAME, StateMsg.class); + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateObjectFactoryImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateObjectFactoryImpl.java new file mode 100644 index 000000000..2c69a98b4 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateObjectFactoryImpl.java @@ -0,0 +1,581 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 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.resolver; + +import java.io.*; +import java.util.*; +import org.eclipse.osgi.internal.module.ResolverImpl; +import org.eclipse.osgi.service.resolver.*; +import org.eclipse.osgi.service.resolver.VersionRange; +import org.eclipse.osgi.storagemanager.StorageManager; +import org.eclipse.osgi.util.ManifestElement; +import org.osgi.framework.*; + +public class StateObjectFactoryImpl implements StateObjectFactory { + + /** + * @deprecated + */ + public BundleDescription createBundleDescription(Dictionary<String, String> manifest, String location, long id) throws BundleException { + return createBundleDescription(null, manifest, location, id); + } + + public BundleDescription createBundleDescription(State state, Dictionary<String, String> manifest, String location, long id) throws BundleException { + BundleDescriptionImpl result = (BundleDescriptionImpl) StateBuilder.createBundleDescription((StateImpl) state, manifest, location); + result.setBundleId(id); + return result; + } + + /** + * @deprecated + */ + public BundleDescription createBundleDescription(long id, String symbolicName, Version version, String location, BundleSpecification[] required, HostSpecification host, ImportPackageSpecification[] imports, ExportPackageDescription[] exports, String[] providedPackages, boolean singleton) { + return createBundleDescription(id, symbolicName, version, location, required, host, imports, exports, providedPackages, singleton, true, true, null, null, null, null); + } + + /** + * @deprecated + */ + public BundleDescription createBundleDescription(long id, String symbolicName, Version version, String location, BundleSpecification[] required, HostSpecification host, ImportPackageSpecification[] imports, ExportPackageDescription[] exports, String[] providedPackages, boolean singleton, boolean attachFragments, boolean dynamicFragments, String platformFilter, String executionEnvironment, GenericSpecification[] genericRequires, GenericDescription[] genericCapabilities) { + // bug 154137 we need to parse the executionEnvironment param; no need to check for null, ManifestElement does that for us. + return createBundleDescription(id, symbolicName, version, location, required, host, imports, exports, singleton, attachFragments, dynamicFragments, platformFilter, ManifestElement.getArrayFromList(executionEnvironment), genericRequires, genericCapabilities); + } + + public BundleDescription createBundleDescription(long id, String symbolicName, Version version, String location, BundleSpecification[] required, HostSpecification host, ImportPackageSpecification[] imports, ExportPackageDescription[] exports, boolean singleton, boolean attachFragments, boolean dynamicFragments, String platformFilter, String[] executionEnvironments, GenericSpecification[] genericRequires, GenericDescription[] genericCapabilities) { + return createBundleDescription(id, symbolicName, version, location, required, host, imports, exports, singleton, attachFragments, dynamicFragments, platformFilter, executionEnvironments, genericRequires, genericCapabilities, null); + } + + public BundleDescription createBundleDescription(long id, String symbolicName, Version version, String location, BundleSpecification[] required, HostSpecification host, ImportPackageSpecification[] imports, ExportPackageDescription[] exports, boolean singleton, boolean attachFragments, boolean dynamicFragments, String platformFilter, String[] executionEnvironments, GenericSpecification[] genericRequires, GenericDescription[] genericCapabilities, NativeCodeSpecification nativeCode) { + BundleDescriptionImpl bundle = new BundleDescriptionImpl(); + bundle.setBundleId(id); + bundle.setSymbolicName(symbolicName); + bundle.setVersion(version); + bundle.setLocation(location); + bundle.setRequiredBundles(required); + bundle.setHost(host); + bundle.setImportPackages(imports); + bundle.setExportPackages(exports); + bundle.setStateBit(BundleDescriptionImpl.SINGLETON, singleton); + bundle.setStateBit(BundleDescriptionImpl.ATTACH_FRAGMENTS, attachFragments); + bundle.setStateBit(BundleDescriptionImpl.DYNAMIC_FRAGMENTS, dynamicFragments); + bundle.setPlatformFilter(platformFilter); + bundle.setExecutionEnvironments(executionEnvironments); + bundle.setGenericRequires(genericRequires); + bundle.setGenericCapabilities(genericCapabilities); + bundle.setNativeCodeSpecification(nativeCode); + return bundle; + } + + public BundleDescription createBundleDescription(long id, String symbolicName, Version version, String location, BundleSpecification[] required, HostSpecification host, ImportPackageSpecification[] imports, ExportPackageDescription[] exports, String platformFilter, String[] executionEnvironments, GenericSpecification[] genericRequires, GenericDescription[] genericCapabilities, NativeCodeSpecification nativeCode) { + BundleDescriptionImpl bundle = new BundleDescriptionImpl(); + bundle.setBundleId(id); + + try { + ManifestElement[] symbolicNameElements = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, symbolicName); + if (symbolicNameElements.length > 0) { + ManifestElement bsnElement = symbolicNameElements[0]; + bundle.setSymbolicName(bsnElement.getValue()); + bundle.setStateBit(BundleDescriptionImpl.SINGLETON, "true".equals(bsnElement.getDirective(Constants.SINGLETON_DIRECTIVE))); //$NON-NLS-1$ + String fragmentAttachment = bsnElement.getDirective(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE); + if (fragmentAttachment != null) { + if (fragmentAttachment.equals(Constants.FRAGMENT_ATTACHMENT_RESOLVETIME)) { + bundle.setStateBit(BundleDescriptionImpl.ATTACH_FRAGMENTS, true); + bundle.setStateBit(BundleDescriptionImpl.DYNAMIC_FRAGMENTS, false); + } else if (fragmentAttachment.equals(Constants.FRAGMENT_ATTACHMENT_NEVER)) { + bundle.setStateBit(BundleDescriptionImpl.ATTACH_FRAGMENTS, false); + bundle.setStateBit(BundleDescriptionImpl.DYNAMIC_FRAGMENTS, false); + } + } + bundle.setDirective(Constants.MANDATORY_DIRECTIVE, ManifestElement.getArrayFromList(bsnElement.getDirective(Constants.MANDATORY_DIRECTIVE))); + bundle.setAttributes(StateBuilder.getAttributes(bsnElement, StateBuilder.DEFINED_BSN_MATCHING_ATTRS)); + bundle.setArbitraryDirectives(StateBuilder.getDirectives(bsnElement, StateBuilder.DEFINED_BSN_DIRECTIVES)); + } + } catch (BundleException e) { + throw (IllegalArgumentException) new IllegalArgumentException("Illegal symbolic name: " + symbolicName).initCause(e); //$NON-NLS-1$ + } + + bundle.setVersion(version); + bundle.setLocation(location); + bundle.setRequiredBundles(required); + bundle.setHost(host); + bundle.setImportPackages(imports); + bundle.setExportPackages(exports); + bundle.setPlatformFilter(platformFilter); + bundle.setExecutionEnvironments(executionEnvironments); + bundle.setGenericRequires(genericRequires); + GenericDescription[] includeIdentity = new GenericDescription[genericCapabilities == null ? 1 : genericCapabilities.length + 1]; + if (genericCapabilities != null) + System.arraycopy(genericCapabilities, 0, includeIdentity, 1, genericCapabilities.length); + includeIdentity[0] = StateBuilder.createOsgiIdentityCapability(bundle); + bundle.setGenericCapabilities(includeIdentity); + bundle.setNativeCodeSpecification(nativeCode); + return bundle; + } + + public BundleDescription createBundleDescription(BundleDescription original) { + BundleDescriptionImpl bundle = new BundleDescriptionImpl(); + bundle.setBundleId(original.getBundleId()); + bundle.setSymbolicName(original.getSymbolicName()); + bundle.setVersion(original.getVersion()); + bundle.setLocation(original.getLocation()); + BundleSpecification[] originalRequired = original.getRequiredBundles(); + BundleSpecification[] newRequired = new BundleSpecification[originalRequired.length]; + for (int i = 0; i < newRequired.length; i++) + newRequired[i] = createBundleSpecification(originalRequired[i]); + bundle.setRequiredBundles(newRequired); + ExportPackageDescription[] originalExports = original.getExportPackages(); + ExportPackageDescription[] newExports = new ExportPackageDescription[originalExports.length]; + for (int i = 0; i < newExports.length; i++) + newExports[i] = createExportPackageDescription(originalExports[i]); + bundle.setExportPackages(newExports); + ImportPackageSpecification[] originalImports = original.getImportPackages(); + ImportPackageSpecification[] newImports = new ImportPackageSpecification[originalImports.length]; + for (int i = 0; i < newImports.length; i++) + newImports[i] = createImportPackageSpecification(originalImports[i]); + bundle.setImportPackages(newImports); + if (original.getHost() != null) + bundle.setHost(createHostSpecification(original.getHost())); + bundle.setStateBit(BundleDescriptionImpl.SINGLETON, original.isSingleton()); + bundle.setStateBit(BundleDescriptionImpl.ATTACH_FRAGMENTS, original.attachFragments()); + bundle.setStateBit(BundleDescriptionImpl.DYNAMIC_FRAGMENTS, original.dynamicFragments()); + bundle.setStateBit(BundleDescriptionImpl.HAS_DYNAMICIMPORT, original.hasDynamicImports()); + bundle.setPlatformFilter(original.getPlatformFilter()); + bundle.setExecutionEnvironments(original.getExecutionEnvironments()); + bundle.setGenericCapabilities(createGenericCapabilities(original.getGenericCapabilities())); + bundle.setGenericRequires(createGenericRequires(original.getGenericRequires())); + bundle.setNativeCodeSpecification(createNativeCodeSpecification(original.getNativeCodeSpecification())); + bundle.setAttributes(original.getAttributes()); + if (original instanceof BundleDescriptionImpl) { + bundle.setDirective(Constants.MANDATORY_DIRECTIVE, ((BundleDescriptionImpl) original).getDirective(Constants.MANDATORY_DIRECTIVE)); + bundle.setArbitraryDirectives(((BundleDescriptionImpl) original).getArbitraryDirectives()); + } + return bundle; + } + + private NativeCodeSpecification createNativeCodeSpecification(NativeCodeSpecification original) { + if (original == null) + return null; + NativeCodeSpecificationImpl result = new NativeCodeSpecificationImpl(); + result.setName(original.getName()); + result.setOptional(original.isOptional()); + NativeCodeDescription[] originalDescriptions = original.getPossibleSuppliers(); + NativeCodeDescriptionImpl[] newDescriptions = new NativeCodeDescriptionImpl[originalDescriptions.length]; + for (int i = 0; i < originalDescriptions.length; i++) { + newDescriptions[i] = new NativeCodeDescriptionImpl(); + newDescriptions[i].setName(originalDescriptions[i].getName()); + newDescriptions[i].setNativePaths(originalDescriptions[i].getNativePaths()); + newDescriptions[i].setProcessors(originalDescriptions[i].getProcessors()); + newDescriptions[i].setOSNames(originalDescriptions[i].getOSNames()); + newDescriptions[i].setOSVersions(originalDescriptions[i].getOSVersions()); + newDescriptions[i].setLanguages(originalDescriptions[i].getLanguages()); + try { + newDescriptions[i].setFilter(originalDescriptions[i].getFilter() == null ? null : originalDescriptions[i].getFilter().toString()); + } catch (InvalidSyntaxException e) { + // this is already tested from the orginal filter + } + } + result.setPossibleSuppliers(newDescriptions); + return result; + } + + private GenericDescription[] createGenericCapabilities(GenericDescription[] genericCapabilities) { + if (genericCapabilities == null || genericCapabilities.length == 0) + return null; + GenericDescription[] result = new GenericDescription[genericCapabilities.length]; + for (int i = 0; i < genericCapabilities.length; i++) { + GenericDescriptionImpl cap = new GenericDescriptionImpl(); + cap.setType(genericCapabilities[i].getType()); + cap.setAttributes(genericCapabilities[i].getAttributes()); + cap.setDirectives(genericCapabilities[i].getDeclaredDirectives()); + result[i] = cap; + } + return result; + } + + private GenericSpecification[] createGenericRequires(GenericSpecification[] genericRequires) { + if (genericRequires == null || genericRequires.length == 0) + return null; + GenericSpecification[] result = new GenericSpecification[genericRequires.length]; + for (int i = 0; i < genericRequires.length; i++) { + GenericSpecificationImpl req = new GenericSpecificationImpl(); + req.setName(genericRequires[i].getName()); + req.setType(genericRequires[i].getType()); + req.setResolution(req.getResolution()); + try { + req.setMatchingFilter(genericRequires[i].getMatchingFilter(), false); + } catch (InvalidSyntaxException e) { + // do nothing; this filter should already have been tested + } + if (genericRequires[i] instanceof GenericSpecificationImpl) { + req.setAttributes(((GenericSpecificationImpl) genericRequires[i]).getAttributes()); + req.setArbitraryDirectives(((GenericSpecificationImpl) genericRequires[i]).getArbitraryDirectives()); + } + result[i] = req; + } + return result; + } + + public BundleSpecification createBundleSpecification(String requiredSymbolicName, VersionRange requiredVersionRange, boolean export, boolean optional) { + BundleSpecificationImpl bundleSpec = new BundleSpecificationImpl(); + bundleSpec.setName(requiredSymbolicName); + bundleSpec.setVersionRange(requiredVersionRange); + bundleSpec.setExported(export); + bundleSpec.setOptional(optional); + return bundleSpec; + } + + public BundleSpecification createBundleSpecification(BundleSpecification original) { + BundleSpecificationImpl bundleSpec = new BundleSpecificationImpl(); + bundleSpec.setName(original.getName()); + bundleSpec.setVersionRange(original.getVersionRange()); + bundleSpec.setExported(original.isExported()); + bundleSpec.setOptional(original.isOptional()); + if (original instanceof BundleSpecificationImpl) { + bundleSpec.setAttributes(((BundleSpecificationImpl) original).getAttributes()); + bundleSpec.setArbitraryDirectives(((BundleSpecificationImpl) original).getArbitraryDirectives()); + } + return bundleSpec; + } + + public HostSpecification createHostSpecification(String hostSymbolicName, VersionRange versionRange) { + HostSpecificationImpl hostSpec = new HostSpecificationImpl(); + hostSpec.setName(hostSymbolicName); + hostSpec.setVersionRange(versionRange); + return hostSpec; + } + + public HostSpecification createHostSpecification(HostSpecification original) { + HostSpecificationImpl hostSpec = new HostSpecificationImpl(); + hostSpec.setName(original.getName()); + hostSpec.setVersionRange(original.getVersionRange()); + if (original instanceof HostSpecificationImpl) { + hostSpec.setAttributes(((HostSpecificationImpl) original).getAttributes()); + hostSpec.setArbitraryDirectives(((HostSpecificationImpl) original).getArbitraryDirectives()); + } + return hostSpec; + } + + public ImportPackageSpecification createImportPackageSpecification(String packageName, VersionRange versionRange, String bundleSymbolicName, VersionRange bundleVersionRange, Map<String, ?> directives, Map<String, ?> attributes, BundleDescription importer) { + ImportPackageSpecificationImpl packageSpec = new ImportPackageSpecificationImpl(); + packageSpec.setName(packageName); + packageSpec.setVersionRange(versionRange); + packageSpec.setBundleSymbolicName(bundleSymbolicName); + packageSpec.setBundleVersionRange(bundleVersionRange); + packageSpec.setDirectives(directives); + packageSpec.setAttributes(attributes); + packageSpec.setBundle(importer); + return packageSpec; + } + + public ImportPackageSpecification createImportPackageSpecification(ImportPackageSpecification original) { + ImportPackageSpecificationImpl packageSpec = new ImportPackageSpecificationImpl(); + packageSpec.setName(original.getName()); + packageSpec.setVersionRange(original.getVersionRange()); + packageSpec.setBundleSymbolicName(original.getBundleSymbolicName()); + packageSpec.setBundleVersionRange(original.getBundleVersionRange()); + packageSpec.setDirectives(original.getDirectives()); + packageSpec.setAttributes(original.getAttributes()); + if (original instanceof ImportPackageSpecificationImpl) { + packageSpec.setArbitraryDirectives(((ImportPackageSpecificationImpl) original).getArbitraryDirectives()); + } + return packageSpec; + } + + public ExportPackageDescription createExportPackageDescription(ExportPackageDescription original) { + ExportPackageDescriptionImpl exportPackage = new ExportPackageDescriptionImpl(); + exportPackage.setName(original.getName()); + exportPackage.setVersion(original.getVersion()); + exportPackage.setDirectives(original.getDirectives()); + exportPackage.setAttributes(original.getAttributes()); + exportPackage.setArbitraryDirectives(((ExportPackageDescriptionImpl) original).getArbitraryDirectives()); + return exportPackage; + } + + public ExportPackageDescription createExportPackageDescription(String packageName, Version version, Map<String, ?> directives, Map<String, ?> attributes, boolean root, BundleDescription exporter) { + ExportPackageDescriptionImpl exportPackage = new ExportPackageDescriptionImpl(); + exportPackage.setName(packageName); + exportPackage.setVersion(version); + exportPackage.setDirectives(directives); + exportPackage.setAttributes(attributes); + exportPackage.setExporter(exporter); + return exportPackage; + } + + /** + * @deprecated + */ + public GenericDescription createGenericDescription(String name, String type, Version version, Map<String, ?> attributes) { + return createGenericDescription(name, type, version, attributes, null, null); + } + + public GenericDescription createGenericDescription(String type, Map<String, ?> attributes, Map<String, String> directives, BundleDescription supplier) { + return createGenericDescription(null, type, null, attributes, directives, supplier); + } + + private GenericDescription createGenericDescription(String name, String type, Version version, Map<String, ?> attributes, Map<String, String> directives, BundleDescription supplier) { + GenericDescriptionImpl result = new GenericDescriptionImpl(); + result.setType(type); + Dictionary<String, Object> attrs = attributes == null ? new Hashtable<String, Object>() : new Hashtable<String, Object>(attributes); + if (version != null) { + Object versionObj = attrs.get(Constants.VERSION_ATTRIBUTE); + if (!(versionObj instanceof Version) && version != null) + attrs.put(Constants.VERSION_ATTRIBUTE, version); + } + if (name != null) { + Object nameObj = attrs.get(result.getType()); + if (!(nameObj instanceof String)) + attrs.put(result.getType(), name); + } + result.setAttributes(attrs); + result.setDirectives(directives); + result.setSupplier(supplier); + return result; + } + + public GenericSpecification createGenericSpecification(String name, String type, String matchingFilter, boolean optional, boolean multiple) throws InvalidSyntaxException { + GenericSpecificationImpl result = new GenericSpecificationImpl(); + result.setName(name); + result.setType(type); + result.setMatchingFilter(matchingFilter, true); + int resolution = 0; + if (optional) + resolution |= GenericSpecification.RESOLUTION_OPTIONAL; + if (multiple) + resolution |= GenericSpecification.RESOLUTION_MULTIPLE; + result.setResolution(resolution); + return result; + } + + public NativeCodeDescription createNativeCodeDescription(String[] nativePaths, String[] processors, String[] osNames, VersionRange[] osVersions, String[] languages, String filter) throws InvalidSyntaxException { + NativeCodeDescriptionImpl result = new NativeCodeDescriptionImpl(); + result.setName(Constants.BUNDLE_NATIVECODE); + result.setNativePaths(nativePaths); + result.setProcessors(processors); + result.setOSNames(osNames); + result.setOSVersions(osVersions); + result.setLanguages(languages); + result.setFilter(filter); + return result; + } + + public NativeCodeSpecification createNativeCodeSpecification(NativeCodeDescription[] nativeCodeDescriptions, boolean optional) { + NativeCodeSpecificationImpl result = new NativeCodeSpecificationImpl(); + result.setName(Constants.BUNDLE_NATIVECODE); + result.setOptional(optional); + result.setPossibleSuppliers(nativeCodeDescriptions); + return result; + } + + /** + * @deprecated + */ + public State createState() { + return internalCreateState(); + } + + public State createState(boolean createResolver) { + State result = internalCreateState(); + if (createResolver) + result.setResolver(new ResolverImpl(false)); + return result; + } + + public State createState(State original) { + StateImpl newState = internalCreateState(); + newState.setTimeStamp(original.getTimeStamp()); + BundleDescription[] bundles = original.getBundles(); + for (int i = 0; i < bundles.length; i++) { + BundleDescription newBundle = createBundleDescription(bundles[i]); + newState.basicAddBundle(newBundle); + DisabledInfo[] infos = original.getDisabledInfos(bundles[i]); + for (int j = 0; j < infos.length; j++) + newState.addDisabledInfo(new DisabledInfo(infos[j].getPolicyName(), infos[j].getMessage(), newBundle)); + } + newState.setResolved(false); + newState.setPlatformProperties(original.getPlatformProperties()); + return newState; + } + + private StateImpl internalCreateState() { + StateImpl state = new UserState(); + state.setFactory(this); + return state; + } + + /** + * @deprecated + */ + public State readState(InputStream stream) throws IOException { + return internalReadStateDeprecated(internalCreateState(), new DataInputStream(stream), -1); + } + + /** + * @deprecated + */ + public State readState(DataInputStream stream) throws IOException { + return internalReadStateDeprecated(internalCreateState(), stream, -1); + } + + public State readState(File stateDirectory) throws IOException { + return internalReadState(internalCreateState(), stateDirectory, -1); + } + + private State internalReadStateDeprecated(StateImpl toRestore, DataInputStream stream, long expectedTimestamp) throws IOException { + StateReader reader = new StateReader(); + if (!reader.loadStateDeprecated(toRestore, stream, expectedTimestamp)) + return null; + return toRestore; + } + + private State internalReadState(StateImpl toRestore, File stateDirectory, long expectedTimestamp) throws IOException { + File stateFile = new File(stateDirectory, StateReader.STATE_FILE); + File lazyFile = new File(stateDirectory, StateReader.LAZY_FILE); + if (!stateFile.exists() || !lazyFile.exists()) { + StorageManager storageManager = new StorageManager(stateDirectory, "none", true); //$NON-NLS-1$ + try { + // if the directory is pointing at the configuration directory then the base files will not exist + storageManager.open(true); + // try using the storage manager to find the managed state files (bug 143255) + File managedState = storageManager.lookup(StateReader.STATE_FILE, false); + File managedLazy = storageManager.lookup(StateReader.LAZY_FILE, false); + if (managedState != null && managedLazy != null) { + stateFile = managedState; + lazyFile = managedLazy; + } + } finally { + storageManager.close(); + } + } + StateReader reader = new StateReader(stateFile, lazyFile, false); + if (!reader.loadState(toRestore, expectedTimestamp)) + return null; + return toRestore; + } + + /** + * @deprecated + */ + public void writeState(State state, DataOutputStream stream) throws IOException { + internalWriteStateDeprecated(state, stream); + } + + public void writeState(State state, File stateDirectory) throws IOException { + if (stateDirectory == null) + throw new IOException(); + StateWriter writer = new StateWriter(); + File stateFile = new File(stateDirectory, StateReader.STATE_FILE); + File lazyFile = new File(stateDirectory, StateReader.LAZY_FILE); + writer.saveState((StateImpl) state, stateFile, lazyFile); + } + + /** + * @deprecated + */ + public void writeState(State state, OutputStream stream) throws IOException { + internalWriteStateDeprecated(state, new DataOutputStream(stream)); + } + + public void writeState(State state, File stateFile, File lazyFile) throws IOException { + StateWriter writer = new StateWriter(); + writer.saveState((StateImpl) state, stateFile, lazyFile); + } + + private void internalWriteStateDeprecated(State state, DataOutputStream stream) throws IOException { + if (state.getFactory() != this) + throw new IllegalArgumentException(); + StateWriter writer = new StateWriter(); + writer.saveStateDeprecated((StateImpl) state, stream); + } + + @SuppressWarnings("unchecked") + public List<BundleSpecification> createBundleSpecifications(String declaration) { + try { + ManifestElement[] elements = ManifestElement.parseHeader(Constants.REQUIRE_BUNDLE, declaration); + if (elements == null) + return Collections.EMPTY_LIST; + List<BundleSpecification> result = new ArrayList<BundleSpecification>(elements.length); + for (ManifestElement element : elements) + result.add(StateBuilder.createRequiredBundle(element)); + return result; + } catch (BundleException e) { + throw (IllegalArgumentException) new IllegalArgumentException("Declaration is invalid: " + declaration).initCause(e); //$NON-NLS-1$ + } + } + + @SuppressWarnings("unchecked") + public List<HostSpecification> createHostSpecifications(String declaration) { + try { + ManifestElement[] elements = ManifestElement.parseHeader(Constants.FRAGMENT_HOST, declaration); + if (elements == null) + return Collections.EMPTY_LIST; + List<HostSpecification> result = new ArrayList<HostSpecification>(elements.length); + for (ManifestElement element : elements) + result.add(StateBuilder.createHostSpecification(element, null)); + return result; + } catch (BundleException e) { + throw (IllegalArgumentException) new IllegalArgumentException("Declaration is invalid: " + declaration).initCause(e); //$NON-NLS-1$ + } + } + + @SuppressWarnings("unchecked") + public List<ImportPackageSpecification> createImportPackageSpecifications(String declaration) { + try { + ManifestElement[] elements = ManifestElement.parseHeader(Constants.IMPORT_PACKAGE, declaration); + if (elements == null) + return Collections.EMPTY_LIST; + List<ImportPackageSpecification> result = new ArrayList<ImportPackageSpecification>(elements.length); + for (ManifestElement element : elements) + StateBuilder.addImportPackages(element, result, 2, false); + return result; + } catch (BundleException e) { + throw (IllegalArgumentException) new IllegalArgumentException("Declaration is invalid: " + declaration).initCause(e); //$NON-NLS-1$ + } + } + + @SuppressWarnings("unchecked") + public List<GenericDescription> createGenericDescriptions(String declaration) { + try { + ManifestElement[] elements = ManifestElement.parseHeader(Constants.PROVIDE_CAPABILITY, declaration); + if (elements == null) + return Collections.EMPTY_LIST; + return StateBuilder.createOSGiCapabilities(elements, new ArrayList<GenericDescription>(elements.length), (Integer) null); + } catch (BundleException e) { + throw (IllegalArgumentException) new IllegalArgumentException("Declaration is invalid: " + declaration).initCause(e); //$NON-NLS-1$ + } + } + + @SuppressWarnings("unchecked") + public List<GenericSpecification> createGenericSpecifications(String declaration) { + try { + ManifestElement[] elements = ManifestElement.parseHeader(Constants.REQUIRE_CAPABILITY, declaration); + if (elements == null) + return Collections.EMPTY_LIST; + return StateBuilder.createOSGiRequires(elements, new ArrayList<GenericSpecification>(elements.length)); + } catch (BundleException e) { + throw (IllegalArgumentException) new IllegalArgumentException("Declaration is invalid: " + declaration).initCause(e); //$NON-NLS-1$ + } + } + + @SuppressWarnings("unchecked") + public List<ExportPackageDescription> createExportPackageDescriptions(String declaration) { + try { + ManifestElement[] elements = ManifestElement.parseHeader(Constants.IMPORT_PACKAGE, declaration); + if (elements == null) + return Collections.EMPTY_LIST; + List<ExportPackageDescription> result = new ArrayList<ExportPackageDescription>(elements.length); + for (ManifestElement element : elements) + StateBuilder.addExportPackages(element, result, false); + return result; + } catch (BundleException e) { + throw (IllegalArgumentException) new IllegalArgumentException("Declaration is invalid: " + declaration).initCause(e); //$NON-NLS-1$ + } + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateReader.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateReader.java new file mode 100644 index 000000000..f121b267a --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateReader.java @@ -0,0 +1,873 @@ +/******************************************************************************* + * Copyright (c) 2003, 2012 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import java.io.*; +import java.lang.reflect.Constructor; +import java.security.AccessController; +import java.util.*; +import java.util.Map.Entry; +import org.eclipse.osgi.framework.util.ObjectPool; +import org.eclipse.osgi.framework.util.SecureAction; +import org.eclipse.osgi.service.resolver.*; +import org.eclipse.osgi.service.resolver.VersionRange; +import org.osgi.framework.*; + +/** + * This class is internally threadsafe and supports client locking. Clients must <strong>not</strong> hold the monitor for + * any {@link StateImpl} or {@link BundleDescriptionImpl} object when calling into the public methods of this class to prevent + * possible deadlock. + */ +final class StateReader { + public static final String STATE_FILE = ".state"; //$NON-NLS-1$ + public static final String LAZY_FILE = ".lazy"; //$NON-NLS-1$ + public static final String UTF_8 = "UTF-8"; //$NON-NLS-1$ + private static final int BUFFER_SIZE_LAZY = 4096; + private static final int BUFFER_SIZE_FULLYREAD = 16384; + private static final SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction()); + + // objectTable will be a hashmap of objects. The objects will be things + // like BundleDescription, ExportPackageDescription, Version etc.. The integer + // index value will be used in the cache to allow cross-references in the + // cached state. + final Map<Integer, Object> objectTable = Collections.synchronizedMap(new HashMap<Integer, Object>()); + + private volatile File stateFile; + private volatile File lazyFile; + + private volatile boolean lazyLoad = true; + private volatile int numBundles; + private volatile boolean accessedFlag = false; + + public static final byte STATE_CACHE_VERSION = 38; + public static final byte NULL = 0; + public static final byte OBJECT = 1; + public static final byte INDEX = 2; + public static final byte LONG_STRING = 3; + + public StateReader() //TODO - deprecated + { + lazyLoad = false; + } + + public StateReader(File stateDirectory) { + if (!stateDirectory.exists()) + stateDirectory.mkdirs(); + this.stateFile = new File(stateDirectory, STATE_FILE); + this.lazyFile = new File(stateDirectory, LAZY_FILE); + this.lazyLoad = false; + } + + public StateReader(File stateFile, File lazyFile, boolean lazyLoad) { + this.stateFile = stateFile; + this.lazyFile = lazyFile; + this.lazyLoad = lazyLoad; + } + + private void addToObjectTable(Object object, int index) { + objectTable.put(new Integer(index), object); + } + + private Object getFromObjectTable(int index) { + Object result = objectTable.get(new Integer(index)); + if (result == null) + throw new IllegalStateException("Expected to find an object at table index: " + index); //$NON-NLS-1$ + return result; + } + + private boolean readState(StateImpl state, long expectedTimestamp) throws IOException { + DataInputStream in = new DataInputStream(new BufferedInputStream(secureAction.getFileInputStream(stateFile), BUFFER_SIZE_FULLYREAD)); + DataInputStream lazyIn = null; + try { + if (in.readByte() != STATE_CACHE_VERSION) + return false; + byte tag = readTag(in); + if (tag != OBJECT) + return false; + int index = in.readInt(); + long timestampRead = in.readLong(); + if (expectedTimestamp >= 0 && timestampRead != expectedTimestamp) + return false; + addToObjectTable(state, index); + // read the platform property keys + String[] platformPropKeys = (String[]) readPlatformProp(in); + state.addPlatformPropertyKeys(platformPropKeys); + int numSets = in.readInt(); + Dictionary<?, ?>[] platformProps = new Dictionary[numSets]; + for (int i = 0; i < numSets; i++) { + Hashtable<Object, Object> props = new Hashtable<Object, Object>(platformPropKeys.length); + int numProps = in.readInt(); + for (int j = 0; j < numProps; j++) { + Object value = readPlatformProp(in); + if (value != null && j < platformPropKeys.length) + props.put(platformPropKeys[j], value); + } + platformProps[i] = props; + } + state.setPlatformProperties(platformProps, false); + numBundles = in.readInt(); + for (int i = 0; i < numBundles; i++) { + BundleDescriptionImpl bundle = readBundleDescription(in); + state.basicAddBundle(bundle); + if (bundle.isResolved()) + state.addResolvedBundle(bundle); + } + // read the DisabledInfos + int numDisableInfos = in.readInt(); + for (int i = 0; i < numDisableInfos; i++) { + DisabledInfo info = readDisabledInfo(in); + state.addDisabledInfo(info); + } + state.setTimeStamp(timestampRead); + state.setResolved(in.readBoolean()); + if (lazyLoad) + return true; + //read in from lazy data file; using the fully read buffer size because we are reading the complete file in. + lazyIn = new DataInputStream(new BufferedInputStream(secureAction.getFileInputStream(lazyFile), BUFFER_SIZE_FULLYREAD)); + for (int i = 0; i < numBundles; i++) + readBundleDescriptionLazyData(lazyIn, 0); + } finally { + in.close(); + if (lazyIn != null) + try { + lazyIn.close(); + } catch (IOException e) { + // ignore + } + } + return true; + } + + private boolean readStateDeprecated(StateImpl state, DataInputStream in, long expectedTimestamp) throws IOException { + if (in.readByte() != STATE_CACHE_VERSION) + return false; + byte tag = readTag(in); + if (tag != OBJECT) + return false; + int index = in.readInt(); + long timestampRead = in.readLong(); + if (expectedTimestamp >= 0 && timestampRead != expectedTimestamp) + return false; + addToObjectTable(state, index); + // read the platform property keys + String[] platformPropKeys = (String[]) readPlatformProp(in); + state.addPlatformPropertyKeys(platformPropKeys); + int numSets = in.readInt(); + Dictionary<?, ?>[] platformProps = new Dictionary[numSets]; + for (int i = 0; i < numSets; i++) { + Hashtable<Object, Object> props = new Hashtable<Object, Object>(platformPropKeys.length); + int numProps = in.readInt(); + for (int j = 0; j < numProps; j++) { + Object value = readPlatformProp(in); + if (value != null && j < platformPropKeys.length) + props.put(platformPropKeys[j], value); + } + platformProps[i] = props; + } + state.setPlatformProperties(platformProps); + numBundles = in.readInt(); + if (numBundles == 0) + return true; + for (int i = 0; i < numBundles; i++) { + BundleDescriptionImpl bundle = readBundleDescription(in); + state.basicAddBundle(bundle); + if (bundle.isResolved()) + state.addResolvedBundle(bundle); + } + state.setTimeStamp(timestampRead); + state.setResolved(in.readBoolean()); + in.readInt(); // skip past the old offset + if (lazyLoad) + return true; + for (int i = 0; i < numBundles; i++) + readBundleDescriptionLazyData(in, 0); + return true; + } + + private Object readPlatformProp(DataInputStream in) throws IOException { + byte type = in.readByte(); + if (type == NULL) + return null; + int num = in.readInt(); + if (num == 1) + return readString(in, false); + String[] result = new String[num]; + for (int i = 0; i < result.length; i++) + result[i] = readString(in, false); + return result; + } + + private BundleDescriptionImpl readBundleDescription(DataInputStream in) throws IOException { + byte tag = readTag(in); + if (tag == NULL) + return null; + if (tag == INDEX) + return (BundleDescriptionImpl) getFromObjectTable(in.readInt()); + // first read in non-lazy loaded data + BundleDescriptionImpl result = new BundleDescriptionImpl(); + addToObjectTable(result, in.readInt()); + + result.setBundleId(in.readLong()); + readBaseDescription(result, in); + result.setLazyDataOffset(in.readInt()); + result.setLazyDataSize(in.readInt()); + result.setStateBit(BundleDescriptionImpl.RESOLVED, in.readBoolean()); + result.setStateBit(BundleDescriptionImpl.SINGLETON, in.readBoolean()); + result.setStateBit(BundleDescriptionImpl.HAS_DYNAMICIMPORT, in.readBoolean()); + result.setStateBit(BundleDescriptionImpl.ATTACH_FRAGMENTS, in.readBoolean()); + result.setStateBit(BundleDescriptionImpl.DYNAMIC_FRAGMENTS, in.readBoolean()); + String[] mandatory = readList(in); + if (mandatory != null) + result.setDirective(Constants.MANDATORY_DIRECTIVE, mandatory); + result.setAttributes(readMap(in)); + result.setArbitraryDirectives(readMap(in)); + result.setHost(readHostSpec(in)); + + // set the bundle dependencies from imports and requires and hosts. + int numDeps = in.readInt(); + if (numDeps > 0) { + BundleDescription[] deps = new BundleDescription[numDeps]; + for (int i = 0; i < numDeps; i++) + deps[i] = readBundleDescription(in); + result.addDependencies(deps, false); // no need to check dups; we already know there are none when we resolved (bug 152900) + } + // No need to set the dependencies between fragment and hosts; that was already done in the above loop (bug 152900) + // but we do need to set the dependencies between hosts and fragment. + HostSpecificationImpl hostSpec = (HostSpecificationImpl) result.getHost(); + if (hostSpec != null) { + BundleDescription[] hosts = hostSpec.getHosts(); + if (hosts != null) { + for (int i = 0; i < hosts.length; i++) + ((BundleDescriptionImpl) hosts[i]).addDependency(result, false); + } + } + // the rest is lazy loaded data + result.setFullyLoaded(false); + return result; + } + + private BundleDescriptionImpl readBundleDescriptionLazyData(DataInputStream in, int skip) throws IOException { + if (skip > 0) + in.skipBytes(skip); + int index = in.readInt(); + BundleDescriptionImpl result = (BundleDescriptionImpl) getFromObjectTable(index); + if (result.isFullyLoaded()) { + in.skipBytes(result.getLazyDataSize() - 4); // skip to the end subtract 4 for the int read already + return result; + } + + result.setLocation(readString(in, false)); + result.setPlatformFilter(readString(in, false)); + + int exportCount = in.readInt(); + if (exportCount > 0) { + ExportPackageDescription[] exports = new ExportPackageDescription[exportCount]; + for (int i = 0; i < exports.length; i++) + exports[i] = readExportPackageDesc(in); + result.setExportPackages(exports); + } + + int importCount = in.readInt(); + if (importCount > 0) { + ImportPackageSpecification[] imports = new ImportPackageSpecification[importCount]; + for (int i = 0; i < imports.length; i++) + imports[i] = readImportPackageSpec(in); + result.setImportPackages(imports); + } + + int requiredBundleCount = in.readInt(); + if (requiredBundleCount > 0) { + BundleSpecification[] requiredBundles = new BundleSpecification[requiredBundleCount]; + for (int i = 0; i < requiredBundles.length; i++) + requiredBundles[i] = readBundleSpec(in); + result.setRequiredBundles(requiredBundles); + } + + int selectedCount = in.readInt(); + if (selectedCount > 0) { + ExportPackageDescription[] selected = new ExportPackageDescription[selectedCount]; + for (int i = 0; i < selected.length; i++) + selected[i] = readExportPackageDesc(in); + result.setSelectedExports(selected); + } + + int substitutedCount = in.readInt(); + if (substitutedCount > 0) { + ExportPackageDescription[] selected = new ExportPackageDescription[substitutedCount]; + for (int i = 0; i < selected.length; i++) + selected[i] = readExportPackageDesc(in); + result.setSubstitutedExports(selected); + } + + int resolvedCount = in.readInt(); + if (resolvedCount > 0) { + ExportPackageDescription[] resolved = new ExportPackageDescription[resolvedCount]; + for (int i = 0; i < resolved.length; i++) + resolved[i] = readExportPackageDesc(in); + result.setResolvedImports(resolved); + } + + int resolvedRequiredCount = in.readInt(); + if (resolvedRequiredCount > 0) { + BundleDescription[] resolved = new BundleDescription[resolvedRequiredCount]; + for (int i = 0; i < resolved.length; i++) + resolved[i] = readBundleDescription(in); + result.setResolvedRequires(resolved); + } + + int eeCount = in.readInt(); + if (eeCount > 0) { + String[] ee = new String[eeCount]; + for (int i = 0; i < ee.length; i++) + ee[i] = readString(in, false); + result.setExecutionEnvironments(ee); + } + + int dynamicPkgCnt = in.readInt(); + if (dynamicPkgCnt > 0) { + HashMap<String, Long> dynamicStamps = new HashMap<String, Long>(dynamicPkgCnt); + for (int i = 0; i < dynamicPkgCnt; i++) { + String pkg = readString(in, false); + Long stamp = new Long(in.readLong()); + dynamicStamps.put(pkg, stamp); + } + result.setDynamicStamps(dynamicStamps); + } + + int genericCapCnt = in.readInt(); + if (genericCapCnt > 0) { + GenericDescription[] capabilities = new GenericDescription[genericCapCnt]; + for (int i = 0; i < capabilities.length; i++) + capabilities[i] = readGenericDescription(in); + result.setGenericCapabilities(capabilities); + } + + int genericReqCnt = in.readInt(); + if (genericReqCnt > 0) { + GenericSpecification[] reqs = new GenericSpecification[genericReqCnt]; + for (int i = 0; i < reqs.length; i++) + reqs[i] = readGenericSpecification(in); + result.setGenericRequires(reqs); + } + + int selectedGenCapCnt = in.readInt(); + if (selectedGenCapCnt > 0) { + GenericDescription[] capabilities = new GenericDescription[selectedGenCapCnt]; + for (int i = 0; i < capabilities.length; i++) + capabilities[i] = readGenericDescription(in); + result.setSelectedCapabilities(capabilities); + } + + int resolvedGenCapCnt = in.readInt(); + if (resolvedGenCapCnt > 0) { + GenericDescription[] capabilities = new GenericDescription[resolvedGenCapCnt]; + for (int i = 0; i < capabilities.length; i++) + capabilities[i] = readGenericDescription(in); + result.setResolvedCapabilities(capabilities); + } + + result.setNativeCodeSpecification(readNativeCode(in)); + + @SuppressWarnings("rawtypes") + Map raw = readMap(in); + result.setStateWires(raw); + + result.setFullyLoaded(true); // set fully loaded before setting the dependencies + // No need to add bundle dependencies for hosts, imports or requires; + // This is done by readBundleDescription + return result; + } + + private BundleSpecificationImpl readBundleSpec(DataInputStream in) throws IOException { + byte tag = readTag(in); + if (tag == NULL) + return null; + if (tag == INDEX) + return (BundleSpecificationImpl) getFromObjectTable(in.readInt()); + BundleSpecificationImpl result = new BundleSpecificationImpl(); + int tableIndex = in.readInt(); + addToObjectTable(result, tableIndex); + readVersionConstraint(result, in); + result.setSupplier(readBundleDescription(in)); + result.setExported(in.readBoolean()); + result.setOptional(in.readBoolean()); + result.setAttributes(readMap(in)); + result.setArbitraryDirectives(readMap(in)); + return result; + } + + private ExportPackageDescriptionImpl readExportPackageDesc(DataInputStream in) throws IOException { + byte tag = readTag(in); + if (tag == NULL) + return null; + if (tag == INDEX) + return (ExportPackageDescriptionImpl) getFromObjectTable(in.readInt()); + ExportPackageDescriptionImpl exportPackageDesc = new ExportPackageDescriptionImpl(); + int tableIndex = in.readInt(); + addToObjectTable(exportPackageDesc, tableIndex); + readBaseDescription(exportPackageDesc, in); + exportPackageDesc.setExporter(readBundleDescription(in)); + exportPackageDesc.setAttributes(readMap(in)); + exportPackageDesc.setDirectives(readMap(in)); + exportPackageDesc.setArbitraryDirectives(readMap(in)); + exportPackageDesc.setFragmentDeclaration(readExportPackageDesc(in)); + return exportPackageDesc; + } + + private DisabledInfo readDisabledInfo(DataInputStream in) throws IOException { + return new DisabledInfo(readString(in, false), readString(in, false), readBundleDescription(in)); + } + + private Map<String, Object> readMap(DataInputStream in) throws IOException { + int count = in.readInt(); + if (count == 0) + return null; + HashMap<String, Object> result = new HashMap<String, Object>(count); + for (int i = 0; i < count; i++) { + String key = readString(in, false); + Object value = null; + byte type = in.readByte(); + if (type == 0) + value = readString(in, false); + else if (type == 1) + value = readList(in); + else if (type == 2) + value = in.readBoolean() ? Boolean.TRUE : Boolean.FALSE; + else if (type == 3) + value = new Integer(in.readInt()); + else if (type == 4) + value = new Long(in.readLong()); + else if (type == 5) + value = new Double(in.readDouble()); + else if (type == 6) + value = readVersion(in); + else if (type == 7) { + value = readString(in, false); + try { + Class<?> uriClazz = Class.forName("java.net.URI"); //$NON-NLS-1$ + Constructor<?> constructor = uriClazz.getConstructor(new Class[] {String.class}); + value = constructor.newInstance(new Object[] {value}); + } catch (ClassNotFoundException e) { + // oh well cannot support; just use the string + } catch (RuntimeException e) { // got some reflection exception + throw e; + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + } else if (type == 8) { + int listType = in.readByte(); + int size = in.readInt(); + List<Object> list = new ArrayList<Object>(size); + for (int j = 0; j < size; j++) { + switch (listType) { + case 0 : + list.add(readString(in, false)); + break; + case 3 : + list.add(new Integer(in.readInt())); + break; + case 4 : + list.add(new Long(in.readLong())); + break; + case 5 : + list.add(new Double(in.readDouble())); + break; + case 6 : + list.add(readVersion(in)); + break; + case 7 : + list.add(readStateWire(in)); + break; + default : + throw new IOException("Invalid type: " + listType); //$NON-NLS-1$ + } + } + value = list; + } + result.put(key, value); + } + return result; + } + + private Object readStateWire(DataInputStream in) throws IOException { + VersionConstraintImpl requirement; + BundleDescription requirementHost; + BaseDescription capability; + BundleDescription capabilityHost; + + byte wireType = in.readByte(); + switch (wireType) { + case 0 : + requirement = readImportPackageSpec(in); + capability = readExportPackageDesc(in); + break; + case 1 : + requirement = readBundleSpec(in); + capability = readBundleDescription(in); + break; + case 2 : + requirement = readHostSpec(in); + capability = readBundleDescription(in); + break; + case 3 : + requirement = readGenericSpecification(in); + capability = readGenericDescription(in); + break; + default : + throw new IOException("Invalid wire type: " + wireType); //$NON-NLS-1$ + } + + requirementHost = readBundleDescription(in); + capabilityHost = readBundleDescription(in); + + if (requirement.getBundle() == null) { + // Need to fix up dynamic imports added by weaving hook (bug 359394) + requirement.setBundle(requirementHost); + } + return new StateWire(requirementHost, requirement, capabilityHost, capability); + } + + private String[] readList(DataInputStream in) throws IOException { + int count = in.readInt(); + if (count == 0) + return null; + String[] result = new String[count]; + for (int i = 0; i < count; i++) + result[i] = readString(in, false); + return result; + } + + private void readBaseDescription(BaseDescriptionImpl root, DataInputStream in) throws IOException { + root.setName(readString(in, false)); + root.setVersion(readVersion(in)); + } + + private ImportPackageSpecificationImpl readImportPackageSpec(DataInputStream in) throws IOException { + byte tag = readTag(in); + if (tag == NULL) + return null; + if (tag == INDEX) + return (ImportPackageSpecificationImpl) getFromObjectTable(in.readInt()); + ImportPackageSpecificationImpl result = new ImportPackageSpecificationImpl(); + int tableIndex = in.readInt(); + addToObjectTable(result, tableIndex); + readVersionConstraint(result, in); + result.setSupplier(readExportPackageDesc(in)); + result.setBundleSymbolicName(readString(in, false)); + result.setBundleVersionRange(readVersionRange(in)); + result.setAttributes(readMap(in)); + result.setDirectives(readMap(in)); + result.setArbitraryDirectives(readMap(in)); + return result; + } + + private HostSpecificationImpl readHostSpec(DataInputStream in) throws IOException { + byte tag = readTag(in); + if (tag == NULL) + return null; + if (tag == INDEX) + return (HostSpecificationImpl) getFromObjectTable(in.readInt()); + HostSpecificationImpl result = new HostSpecificationImpl(); + int tableIndex = in.readInt(); + addToObjectTable(result, tableIndex); + readVersionConstraint(result, in); + int hostCount = in.readInt(); + if (hostCount > 0) { + BundleDescription[] hosts = new BundleDescription[hostCount]; + for (int i = 0; i < hosts.length; i++) + hosts[i] = readBundleDescription(in); + result.setHosts(hosts); + } + result.setAttributes(readMap(in)); + result.setArbitraryDirectives(readMap(in)); + return result; + } + + private GenericDescription readGenericDescription(DataInputStream in) throws IOException { + byte tag = readTag(in); + if (tag == NULL) + return null; + if (tag == INDEX) + return (GenericDescription) getFromObjectTable(in.readInt()); + int tableIndex = in.readInt(); + GenericDescriptionImpl result = new GenericDescriptionImpl(); + addToObjectTable(result, tableIndex); + readBaseDescription(result, in); + result.setSupplier(readBundleDescription(in)); + result.setType(readString(in, false)); + Map<String, Object> mapAttrs = readMap(in); + Dictionary<String, Object> attrs = new Hashtable<String, Object>(); + if (mapAttrs != null) { + for (Iterator<String> keys = mapAttrs.keySet().iterator(); keys.hasNext();) { + String key = keys.next(); + attrs.put(key, mapAttrs.get(key)); + } + } + result.setAttributes(attrs); + Map directives = readMap(in); + if (directives != null) + result.setDirectives(directives); + result.setFragmentDeclaration(readGenericDescription(in)); + return result; + } + + private GenericSpecificationImpl readGenericSpecification(DataInputStream in) throws IOException { + byte tag = readTag(in); + if (tag == NULL) + return null; + if (tag == INDEX) + return (GenericSpecificationImpl) getFromObjectTable(in.readInt()); + GenericSpecificationImpl result = new GenericSpecificationImpl(); + int tableIndex = in.readInt(); + addToObjectTable(result, tableIndex); + readVersionConstraint(result, in); + result.setType(readString(in, false)); + int num = in.readInt(); + GenericDescription[] suppliers = num == 0 ? null : new GenericDescription[num]; + for (int i = 0; i < num; i++) + suppliers[i] = readGenericDescription(in); + result.setSupplers(suppliers); + result.setResolution(in.readInt()); + try { + result.setMatchingFilter(readString(in, false), false); + } catch (InvalidSyntaxException e) { + // do nothing this filter was tested before + } + result.setAttributes(readMap(in)); + result.setArbitraryDirectives(readMap(in)); + return result; + } + + private NativeCodeSpecification readNativeCode(DataInputStream in) throws IOException { + if (!in.readBoolean()) + return null; + NativeCodeSpecificationImpl result = new NativeCodeSpecificationImpl(); + result.setOptional(in.readBoolean()); + int numNativeDesc = in.readInt(); + NativeCodeDescriptionImpl[] nativeDescs = new NativeCodeDescriptionImpl[numNativeDesc]; + for (int i = 0; i < numNativeDesc; i++) + nativeDescs[i] = readNativeCodeDescription(in); + result.setPossibleSuppliers(nativeDescs); + int supplierIndex = in.readInt(); + if (supplierIndex >= 0) + result.setSupplier(nativeDescs[supplierIndex]); + return result; + } + + private NativeCodeDescriptionImpl readNativeCodeDescription(DataInputStream in) throws IOException { + NativeCodeDescriptionImpl result = new NativeCodeDescriptionImpl(); + readBaseDescription(result, in); + result.setSupplier(readBundleDescription(in)); + try { + result.setFilter(readString(in, false)); + } catch (InvalidSyntaxException e) { + // do nothing, this filter was tested before + } + result.setLanguages(readStringArray(in)); + result.setNativePaths(readStringArray(in)); + result.setOSNames(readStringArray(in)); + result.setOSVersions(readVersionRanges(in)); + result.setProcessors(readStringArray(in)); + result.setInvalidNativePaths(in.readBoolean()); + return result; + } + + private VersionRange[] readVersionRanges(DataInputStream in) throws IOException { + int num = in.readInt(); + if (num == 0) + return null; + VersionRange[] result = new VersionRange[num]; + for (int i = 0; i < num; i++) + result[i] = readVersionRange(in); + return result; + } + + private String[] readStringArray(DataInputStream in) throws IOException { + int num = in.readInt(); + if (num == 0) + return null; + String[] result = new String[num]; + for (int i = 0; i < num; i++) + result[i] = readString(in, false); + return result; + } + + // called by readers for VersionConstraintImpl subclasses + private void readVersionConstraint(VersionConstraintImpl version, DataInputStream in) throws IOException { + version.setName(readString(in, false)); + version.setVersionRange(readVersionRange(in)); + } + + private Version readVersion(DataInputStream in) throws IOException { + byte tag = readTag(in); + if (tag == NULL) + return Version.emptyVersion; + int majorComponent = in.readInt(); + int minorComponent = in.readInt(); + int serviceComponent = in.readInt(); + String qualifierComponent = readString(in, false); + Version result = (Version) ObjectPool.intern(new Version(majorComponent, minorComponent, serviceComponent, qualifierComponent)); + //Version result = new Version(majorComponent, minorComponent, serviceComponent, qualifierComponent); + return result; + } + + private VersionRange readVersionRange(DataInputStream in) throws IOException { + byte tag = readTag(in); + if (tag == NULL) + return null; + return new VersionRange(readVersion(in), in.readBoolean(), readVersion(in), in.readBoolean()); + } + + /** + * expectedTimestamp is the expected value for the timestamp. or -1, if + * no checking should be performed + */ + public synchronized boolean loadStateDeprecated(StateImpl state, DataInputStream input, long expectedTimestamp) throws IOException { + try { + return readStateDeprecated(state, input, expectedTimestamp); + } finally { + input.close(); + } + } + + /** + * expectedTimestamp is the expected value for the timestamp. or -1, if + * no checking should be performed + */ + public synchronized boolean loadState(StateImpl state, long expectedTimestamp) throws IOException { + return readState(state, expectedTimestamp); + } + + private String readString(DataInputStream in, boolean intern) throws IOException { + byte type = in.readByte(); + if (type == NULL) + return null; + + if (type == LONG_STRING) { + int length = in.readInt(); + byte[] data = new byte[length]; + in.readFully(data); + String string = new String(data, UTF_8); + + if (intern) + return string.intern(); + return (String) ObjectPool.intern(string); + } + + if (intern) + return in.readUTF().intern(); + return (String) ObjectPool.intern(in.readUTF()); + } + + private byte readTag(DataInputStream in) throws IOException { + return in.readByte(); + } + + private DataInputStream openLazyFile() throws IOException { + if (lazyFile == null) + throw new IOException(); // TODO error message here! + return new DataInputStream(new BufferedInputStream(secureAction.getFileInputStream(lazyFile), BUFFER_SIZE_LAZY)); + } + + boolean isLazyLoaded() { + return lazyLoad; + } + + boolean getAccessedFlag() { + return accessedFlag; + } + + void setAccessedFlag(boolean accessedFlag) { + this.accessedFlag = accessedFlag; + } + + void fullyLoad() { + setAccessedFlag(true); + DataInputStream in = null; + try { + in = openLazyFile(); + for (int i = 0; i < numBundles; i++) + readBundleDescriptionLazyData(in, 0); + } catch (IOException ioe) { + throw new RuntimeException(ioe.getMessage(), ioe); // TODO need error message here + } finally { + if (in != null) + try { + in.close(); + } catch (IOException e) { + // nothing we can do now + } + } + } + + void fullyLoad(BundleDescriptionImpl target) throws IOException { + setAccessedFlag(true); + DataInputStream in = null; + try { + in = openLazyFile(); + // get the set of bundles that must be loaded according to dependencies + List<BundleDescriptionImpl> toLoad = new ArrayList<BundleDescriptionImpl>(); + addDependencies(target, toLoad); + int skipBytes[] = getSkipBytes(toLoad); + // look for the lazy data of the toLoad list + for (int i = 0; i < skipBytes.length; i++) + readBundleDescriptionLazyData(in, skipBytes[i]); + } finally { + if (in != null) + in.close(); + } + } + + private void addDependencies(BundleDescriptionImpl target, List<BundleDescriptionImpl> toLoad) { + if (toLoad.contains(target) || target.isFullyLoaded()) + return; + Iterator<BundleDescriptionImpl> load = toLoad.iterator(); + int i = 0; + while (load.hasNext()) { + // insert the target into the list sorted by lazy data offsets + BundleDescriptionImpl bundle = load.next(); + if (target.getLazyDataOffset() < bundle.getLazyDataOffset()) + break; + i++; + } + if (i >= toLoad.size()) + toLoad.add(target); + else + toLoad.add(i, target); + List<BundleDescription> deps = target.getBundleDependencies(); + for (Iterator<BundleDescription> iter = deps.iterator(); iter.hasNext();) + addDependencies((BundleDescriptionImpl) iter.next(), toLoad); + } + + private int[] getSkipBytes(List<BundleDescriptionImpl> toLoad) { + int[] skipBytes = new int[toLoad.size()]; + for (int i = 0; i < skipBytes.length; i++) { + BundleDescriptionImpl current = toLoad.get(i); + if (i == 0) { + skipBytes[i] = current.getLazyDataOffset(); + continue; + } + BundleDescriptionImpl previous = toLoad.get(i - 1); + skipBytes[i] = current.getLazyDataOffset() - previous.getLazyDataOffset() - previous.getLazyDataSize(); + } + return skipBytes; + } + + void flushLazyObjectCache() { + for (Iterator<Entry<Integer, Object>> entries = objectTable.entrySet().iterator(); entries.hasNext();) { + Map.Entry<Integer, Object> entry = entries.next(); + Object value = entry.getValue(); + if (value instanceof ExportPackageDescription || value instanceof GenericDescription || value instanceof ImportPackageSpecification || value instanceof BundleSpecification || value instanceof GenericSpecification) + entries.remove(); + } + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateWriter.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateWriter.java new file mode 100644 index 000000000..57a10d0eb --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/StateWriter.java @@ -0,0 +1,711 @@ +/******************************************************************************* + * Copyright (c) 2003, 2012 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import java.io.*; +import java.util.*; +import org.eclipse.osgi.service.resolver.*; +import org.eclipse.osgi.service.resolver.VersionRange; +import org.osgi.framework.*; + +/** + * This class is <strong>not</strong> thread safe. Instances must not be + * shared across multiple threads. + */ +class StateWriter { + + // objectTable will be a hashmap of objects. The objects will be things + // like BundleDescription, ExportPackageDescription, Version etc.. The integer + // index value will be used in the cache to allow cross-references in the + // cached state. + private final Map<Object, Integer> objectTable = new HashMap<Object, Integer>(); + + private final List<Object> forcedWrite = new ArrayList<Object>(); + + private int addToObjectTable(Object object) { + Integer cur = objectTable.get(object); + if (cur != null) + return cur.intValue(); + objectTable.put(object, new Integer(objectTable.size())); + // return the index of the object just added (i.e. size - 1) + return (objectTable.size() - 1); + } + + private int getFromObjectTable(Object object) { + if (objectTable != null) { + Object objectResult = objectTable.get(object); + if (objectResult != null) { + return ((Integer) objectResult).intValue(); + } + } + return -1; + } + + private boolean writePrefix(Object object, DataOutputStream out) throws IOException { + if (writeIndex(object, out)) + return true; + // add this object to the object table first + int index = addToObjectTable(object); + out.writeByte(StateReader.OBJECT); + out.writeInt(index); + return false; + } + + private void writeStateDeprecated(StateImpl state, DataOutputStream out) throws IOException { + out.write(StateReader.STATE_CACHE_VERSION); + if (writePrefix(state, out)) + return; + out.writeLong(state.getTimeStamp()); + // write the platform property keys + String[] platformPropKeys = state.getPlatformPropertyKeys(); + writePlatformProp(platformPropKeys, out); + Dictionary<Object, Object>[] propSet = state.getPlatformProperties(); + out.writeInt(propSet.length); + for (int i = 0; i < propSet.length; i++) { + Dictionary<Object, Object> props = propSet[i]; + out.writeInt(platformPropKeys.length); + for (int j = 0; j < platformPropKeys.length; j++) + writePlatformProp(props.get(platformPropKeys[j]), out); + } + BundleDescription[] bundles = state.getBundles(); + StateHelperImpl.getInstance().sortBundles(bundles); + out.writeInt(bundles.length); + if (bundles.length == 0) + return; + for (int i = 0; i < bundles.length; i++) + writeBundleDescription(bundles[i], out, false); + out.writeBoolean(state.isResolved()); + // save the lazy data offset + out.writeInt(out.size()); + for (int i = 0; i < bundles.length; i++) + writeBundleDescriptionLazyData(bundles[i], out); + } + + public void saveState(StateImpl state, File stateFile, File lazyFile) throws IOException { + DataOutputStream outLazy = null; + DataOutputStream outState = null; + FileOutputStream fosLazy = null; + FileOutputStream fosState = null; + synchronized (state.monitor) { + try { + BundleDescription[] bundles = state.getBundles(); + StateHelperImpl.getInstance().sortBundles(bundles); + // need to prime the object table with all bundles + // this allows us to write only indexes to bundles in the lazy data + for (int i = 0; i < bundles.length; i++) { + addToObjectTable(bundles[i]); + if (bundles[i].getHost() != null) + addToObjectTable(bundles[i].getHost()); + } + // first write the lazy data to get the offsets and sizes to the lazy data + fosLazy = new FileOutputStream(lazyFile); + outLazy = new DataOutputStream(new BufferedOutputStream(fosLazy)); + for (int i = 0; i < bundles.length; i++) + writeBundleDescriptionLazyData(bundles[i], outLazy); + // now write the state data + fosState = new FileOutputStream(stateFile); + outState = new DataOutputStream(new BufferedOutputStream(fosState)); + outState.write(StateReader.STATE_CACHE_VERSION); + if (writePrefix(state, outState)) + return; + outState.writeLong(state.getTimeStamp()); + // write the platform property keys + String[] platformPropKeys = state.getPlatformPropertyKeys(); + writePlatformProp(platformPropKeys, outState); + // write the platform property values + Dictionary<Object, Object>[] propSet = state.getPlatformProperties(); + outState.writeInt(propSet.length); + for (int i = 0; i < propSet.length; i++) { + Dictionary<Object, Object> props = propSet[i]; + outState.writeInt(platformPropKeys.length); + for (int j = 0; j < platformPropKeys.length; j++) + writePlatformProp(props.get(platformPropKeys[j]), outState); + } + outState.writeInt(bundles.length); + for (int i = 0; i < bundles.length; i++) + // write out each bundle with the force flag set to make sure + // the data is written at least once in the non-lazy state data + writeBundleDescription(bundles[i], outState, true); + // write the DisabledInfos + DisabledInfo[] infos = state.getDisabledInfos(); + outState.writeInt(infos.length); + for (int i = 0; i < infos.length; i++) + writeDisabledInfo(infos[i], outState); + outState.writeBoolean(state.isResolved()); + } finally { + if (outLazy != null) { + try { + outLazy.flush(); + fosLazy.getFD().sync(); + } catch (IOException e) { + // do nothing, we tried + } + try { + outLazy.close(); + } catch (IOException e) { + // do nothing + } + } + if (outState != null) { + try { + outState.flush(); + fosState.getFD().sync(); + } catch (IOException e) { + // do nothing, we tried + } + try { + outState.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + } + + private void writePlatformProp(Object obj, DataOutputStream out) throws IOException { + if (!(obj instanceof String) && !(obj instanceof String[])) + out.writeByte(StateReader.NULL); + else { + out.writeByte(StateReader.OBJECT); + if (obj instanceof String) { + out.writeInt(1); + writeStringOrNull((String) obj, out); + } else { + String[] props = (String[]) obj; + out.writeInt(props.length); + for (int i = 0; i < props.length; i++) + writeStringOrNull(props[i], out); + } + } + } + + /* + * The force flag is used when writing the non-lazy state data. This forces the data to be + * written once even if the object exists in the object table. + * This is needed because we want to write the lazy data first but we only want + * to include indexes to the actual bundles in the lazy data. To do this we + * prime the object table with all the bundles first. Then we write the + * lazy data. Finally we write the non-lazy data and force a write of the + * bundles data once even if the bundle is in the object table. + */ + private void writeBundleDescription(BundleDescription bundle, DataOutputStream out, boolean force) throws IOException { + if (force && !forcedWrite.contains(bundle)) { + int index = addToObjectTable(bundle); + out.writeByte(StateReader.OBJECT); + out.writeInt(index); + forcedWrite.add(bundle); + } else if (writePrefix(bundle, out)) + return; + // first write out non-lazy loaded data + out.writeLong(bundle.getBundleId()); // ID must be the first thing + writeBaseDescription(bundle, out); + out.writeInt(((BundleDescriptionImpl) bundle).getLazyDataOffset()); + out.writeInt(((BundleDescriptionImpl) bundle).getLazyDataSize()); + out.writeBoolean(bundle.isResolved()); + out.writeBoolean(bundle.isSingleton()); + out.writeBoolean(bundle.hasDynamicImports()); + out.writeBoolean(bundle.attachFragments()); + out.writeBoolean(bundle.dynamicFragments()); + writeList(out, (String[]) ((BundleDescriptionImpl) bundle).getDirective(Constants.MANDATORY_DIRECTIVE)); + writeMap(out, bundle.getAttributes()); + writeMap(out, ((BundleDescriptionImpl) bundle).getArbitraryDirectives()); + writeHostSpec((HostSpecificationImpl) bundle.getHost(), out, force); + + List<BundleDescription> dependencies = ((BundleDescriptionImpl) bundle).getBundleDependencies(); + out.writeInt(dependencies.size()); + for (Iterator<BundleDescription> iter = dependencies.iterator(); iter.hasNext();) + writeBundleDescription(iter.next(), out, force); + // the rest is lazy loaded data + } + + private void writeBundleDescriptionLazyData(BundleDescription bundle, DataOutputStream out) throws IOException { + int dataStart = out.size(); // save the offset of lazy data start + int index = getFromObjectTable(bundle); + ((BundleDescriptionImpl) bundle).setLazyDataOffset(out.size()); + out.writeInt(index); + + writeStringOrNull(bundle.getLocation(), out); + writeStringOrNull(bundle.getPlatformFilter(), out); + + ExportPackageDescription[] exports = bundle.getExportPackages(); + out.writeInt(exports.length); + for (int i = 0; i < exports.length; i++) + writeExportPackageDesc((ExportPackageDescriptionImpl) exports[i], out); + + ImportPackageSpecification[] imports = bundle.getImportPackages(); + out.writeInt(imports.length); + for (int i = 0; i < imports.length; i++) + writeImportPackageSpec((ImportPackageSpecificationImpl) imports[i], out); + + BundleSpecification[] requiredBundles = bundle.getRequiredBundles(); + out.writeInt(requiredBundles.length); + for (int i = 0; i < requiredBundles.length; i++) + writeBundleSpec((BundleSpecificationImpl) requiredBundles[i], out); + + ExportPackageDescription[] selectedExports = bundle.getSelectedExports(); + if (selectedExports == null) { + out.writeInt(0); + } else { + out.writeInt(selectedExports.length); + for (int i = 0; i < selectedExports.length; i++) + writeExportPackageDesc((ExportPackageDescriptionImpl) selectedExports[i], out); + } + + ExportPackageDescription[] substitutedExports = bundle.getSubstitutedExports(); + if (substitutedExports == null) { + out.writeInt(0); + } else { + out.writeInt(substitutedExports.length); + for (int i = 0; i < substitutedExports.length; i++) + writeExportPackageDesc((ExportPackageDescriptionImpl) substitutedExports[i], out); + } + + ExportPackageDescription[] resolvedImports = bundle.getResolvedImports(); + if (resolvedImports == null) { + out.writeInt(0); + } else { + out.writeInt(resolvedImports.length); + for (int i = 0; i < resolvedImports.length; i++) + writeExportPackageDesc((ExportPackageDescriptionImpl) resolvedImports[i], out); + } + + BundleDescription[] resolvedRequires = bundle.getResolvedRequires(); + if (resolvedRequires == null) { + out.writeInt(0); + } else { + out.writeInt(resolvedRequires.length); + for (int i = 0; i < resolvedRequires.length; i++) + writeBundleDescription(resolvedRequires[i], out, false); + } + + String[] ees = bundle.getExecutionEnvironments(); + out.writeInt(ees.length); + for (int i = 0; i < ees.length; i++) + writeStringOrNull(ees[i], out); + + Map<String, Long> dynamicStamps = ((BundleDescriptionImpl) bundle).getDynamicStamps(); + if (dynamicStamps == null) + out.writeInt(0); + else { + out.writeInt(dynamicStamps.size()); + for (Iterator<String> pkgs = dynamicStamps.keySet().iterator(); pkgs.hasNext();) { + String pkg = pkgs.next(); + writeStringOrNull(pkg, out); + out.writeLong(dynamicStamps.get(pkg).longValue()); + } + } + + GenericDescription[] genericCapabilities = bundle.getGenericCapabilities(); + if (genericCapabilities == null) + out.writeInt(0); + else { + out.writeInt(genericCapabilities.length); + for (int i = 0; i < genericCapabilities.length; i++) + writeGenericDescription(genericCapabilities[i], out); + } + + GenericSpecification[] genericRequires = bundle.getGenericRequires(); + if (genericRequires == null) + out.writeInt(0); + else { + out.writeInt(genericRequires.length); + for (int i = 0; i < genericRequires.length; i++) + writeGenericSpecification((GenericSpecificationImpl) genericRequires[i], out); + } + + GenericDescription[] selectedCapabilities = bundle.getSelectedGenericCapabilities(); + if (selectedCapabilities == null) + out.writeInt(0); + else { + out.writeInt(selectedCapabilities.length); + for (int i = 0; i < selectedCapabilities.length; i++) + writeGenericDescription(selectedCapabilities[i], out); + } + + GenericDescription[] resolvedCapabilities = bundle.getResolvedGenericRequires(); + if (resolvedCapabilities == null) + out.writeInt(0); + else { + out.writeInt(resolvedCapabilities.length); + for (int i = 0; i < resolvedCapabilities.length; i++) + writeGenericDescription(resolvedCapabilities[i], out); + } + + writeNativeCode(bundle.getNativeCodeSpecification(), out); + + writeMap(out, ((BundleDescriptionImpl) bundle).getWiresInternal()); + // save the size of the lazy data + ((BundleDescriptionImpl) bundle).setLazyDataSize(out.size() - dataStart); + } + + private void writeDisabledInfo(DisabledInfo disabledInfo, DataOutputStream out) throws IOException { + writeStringOrNull(disabledInfo.getPolicyName(), out); + writeStringOrNull(disabledInfo.getMessage(), out); + writeBundleDescription(disabledInfo.getBundle(), out, false); + } + + private void writeBundleSpec(BundleSpecificationImpl bundle, DataOutputStream out) throws IOException { + if (writePrefix(bundle, out)) + return; + writeVersionConstraint(bundle, out); + writeBundleDescription((BundleDescription) bundle.getSupplier(), out, false); + out.writeBoolean(bundle.isExported()); + out.writeBoolean(bundle.isOptional()); + writeMap(out, bundle.getAttributes()); + writeMap(out, bundle.getArbitraryDirectives()); + } + + private void writeExportPackageDesc(ExportPackageDescriptionImpl exportPackageDesc, DataOutputStream out) throws IOException { + if (writePrefix(exportPackageDesc, out)) + return; + writeBaseDescription(exportPackageDesc, out); + writeBundleDescription(exportPackageDesc.getExporter(), out, false); + writeMap(out, exportPackageDesc.getAttributes()); + writeMap(out, exportPackageDesc.getDirectives()); + writeMap(out, exportPackageDesc.getArbitraryDirectives()); + writeExportPackageDesc((ExportPackageDescriptionImpl) exportPackageDesc.getFragmentDeclaration(), out); + } + + private void writeGenericDescription(GenericDescription description, DataOutputStream out) throws IOException { + if (writePrefix(description, out)) + return; + writeBaseDescription(description, out); + writeBundleDescription(description.getSupplier(), out, false); + writeStringOrNull(description.getType() == GenericDescription.DEFAULT_TYPE ? null : description.getType(), out); + Dictionary<String, Object> attrs = description.getAttributes(); + Map<String, Object> mapAttrs = new HashMap<String, Object>(attrs.size()); + for (Enumeration<String> keys = attrs.keys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + mapAttrs.put(key, attrs.get(key)); + } + writeMap(out, mapAttrs); + Map<String, String> directives = description.getDeclaredDirectives(); + writeMap(out, directives); + writeGenericDescription((GenericDescription) ((BaseDescriptionImpl) description).getFragmentDeclaration(), out); + } + + private void writeGenericSpecification(GenericSpecificationImpl specification, DataOutputStream out) throws IOException { + if (writePrefix(specification, out)) + return; + writeVersionConstraint(specification, out); + writeStringOrNull(specification.getType() == GenericDescription.DEFAULT_TYPE ? null : specification.getType(), out); + GenericDescription[] suppliers = specification.getSuppliers(); + out.writeInt(suppliers == null ? 0 : suppliers.length); + if (suppliers != null) + for (int i = 0; i < suppliers.length; i++) + writeGenericDescription(suppliers[i], out); + out.writeInt(specification.getResolution()); + writeStringOrNull(specification.getMatchingFilter(), out); + writeMap(out, specification.getAttributes()); + writeMap(out, specification.getArbitraryDirectives()); + } + + private void writeNativeCode(NativeCodeSpecification nativeCodeSpecification, DataOutputStream out) throws IOException { + if (nativeCodeSpecification == null) { + out.writeBoolean(false); + return; + } + out.writeBoolean(true); + out.writeBoolean(nativeCodeSpecification.isOptional()); + NativeCodeDescription[] nativeDescs = nativeCodeSpecification.getPossibleSuppliers(); + int numDescs = nativeDescs == null ? 0 : nativeDescs.length; + out.writeInt(numDescs); + int supplierIndex = -1; + for (int i = 0; i < numDescs; i++) { + if (nativeDescs[i] == nativeCodeSpecification.getSupplier()) + supplierIndex = i; + writeNativeCodeDescription(nativeDescs[i], out); + } + out.writeInt(supplierIndex); + } + + private void writeNativeCodeDescription(NativeCodeDescription nativeCodeDescription, DataOutputStream out) throws IOException { + writeBaseDescription(nativeCodeDescription, out); + writeBundleDescription(nativeCodeDescription.getSupplier(), out, false); + Filter filter = nativeCodeDescription.getFilter(); + writeStringOrNull(filter == null ? null : filter.toString(), out); + writeStringArray(nativeCodeDescription.getLanguages(), out); + writeStringArray(nativeCodeDescription.getNativePaths(), out); + writeStringArray(nativeCodeDescription.getOSNames(), out); + writeVersionRanges(nativeCodeDescription.getOSVersions(), out); + writeStringArray(nativeCodeDescription.getProcessors(), out); + out.writeBoolean(nativeCodeDescription.hasInvalidNativePaths()); + } + + private void writeVersionRanges(VersionRange[] ranges, DataOutputStream out) throws IOException { + out.writeInt(ranges == null ? 0 : ranges.length); + if (ranges == null) + return; + for (int i = 0; i < ranges.length; i++) + writeVersionRange(ranges[i], out); + } + + private void writeStringArray(String[] strings, DataOutputStream out) throws IOException { + out.writeInt(strings == null ? 0 : strings.length); + if (strings == null) + return; + for (int i = 0; i < strings.length; i++) + writeStringOrNull(strings[i], out); + } + + private void writeMap(DataOutputStream out, Map<String, ?> source) throws IOException { + if (source == null) { + out.writeInt(0); + } else { + out.writeInt(source.size()); + Iterator<String> iter = source.keySet().iterator(); + while (iter.hasNext()) { + String key = iter.next(); + Object value = source.get(key); + writeStringOrNull(key, out); + if (value instanceof String) { + out.writeByte(0); + writeStringOrNull((String) value, out); + } else if (value instanceof String[]) { + out.writeByte(1); + writeList(out, (String[]) value); + } else if (value instanceof Boolean) { + out.writeByte(2); + out.writeBoolean(((Boolean) value).booleanValue()); + } else if (value instanceof Integer) { + out.writeByte(3); + out.writeInt(((Integer) value).intValue()); + } else if (value instanceof Long) { + out.writeByte(4); + out.writeLong(((Long) value).longValue()); + } else if (value instanceof Double) { + out.writeByte(5); + out.writeDouble(((Double) value).doubleValue()); + } else if (value instanceof Version) { + out.writeByte(6); + writeVersion((Version) value, out); + } else if ("java.net.URI".equals(value.getClass().getName())) { //$NON-NLS-1$ + out.writeByte(7); + writeStringOrNull(value.toString(), out); + } else if (value instanceof List) { + writeList(out, (List<?>) value); + } + } + } + } + + private void writeList(DataOutputStream out, List<?> list) throws IOException { + byte type = getListType(list); + if (type == -2) + return; // don't understand the list type + out.writeByte(8); + out.writeByte(type); + out.writeInt(list.size()); + for (Object value : list) { + switch (type) { + case 0 : + writeStringOrNull((String) value, out); + break; + case 3 : + out.writeInt(((Integer) value).intValue()); + break; + case 4 : + out.writeLong(((Long) value).longValue()); + break; + case 5 : + out.writeDouble(((Double) value).doubleValue()); + break; + case 6 : + writeVersion((Version) value, out); + break; + case 7 : + writeStateWire((StateWire) value, out); + default : + break; + } + } + } + + private void writeStateWire(StateWire wire, DataOutputStream out) throws IOException { + VersionConstraint requirement = wire.getDeclaredRequirement(); + if (requirement instanceof ImportPackageSpecificationImpl) { + out.writeByte(0); + writeImportPackageSpec((ImportPackageSpecificationImpl) requirement, out); + } else if (requirement instanceof BundleSpecificationImpl) { + out.writeByte(1); + writeBundleSpec((BundleSpecificationImpl) requirement, out); + } else if (requirement instanceof HostSpecificationImpl) { + out.writeByte(2); + writeHostSpec((HostSpecificationImpl) requirement, out, false); + } else if (requirement instanceof GenericSpecificationImpl) { + out.writeByte(3); + writeGenericSpecification((GenericSpecificationImpl) requirement, out); + } else + throw new IllegalArgumentException("Unknown requiement type: " + requirement.getClass()); + + BaseDescription capability = wire.getDeclaredCapability(); + if (capability instanceof BundleDescription) + writeBundleDescription((BundleDescription) capability, out, false); + else if (capability instanceof ExportPackageDescriptionImpl) + writeExportPackageDesc((ExportPackageDescriptionImpl) capability, out); + else if (capability instanceof GenericDescription) + writeGenericDescription((GenericDescription) capability, out); + else + throw new IllegalArgumentException("Unknown capability type: " + requirement.getClass()); + + writeBundleDescription(wire.getRequirementHost(), out, false); + writeBundleDescription(wire.getCapabilityHost(), out, false); + } + + private byte getListType(List<?> list) { + if (list.size() == 0) + return -1; + Object type = list.get(0); + if (type instanceof String) + return 0; + if (type instanceof Integer) + return 3; + if (type instanceof Long) + return 4; + if (type instanceof Double) + return 5; + if (type instanceof Version) + return 6; + if (type instanceof StateWire) + return 7; + return -2; + } + + private void writeList(DataOutputStream out, String[] list) throws IOException { + if (list == null) { + out.writeInt(0); + } else { + out.writeInt(list.length); + for (int i = 0; i < list.length; i++) + writeStringOrNull(list[i], out); + } + } + + private void writeBaseDescription(BaseDescription rootDesc, DataOutputStream out) throws IOException { + writeStringOrNull(rootDesc.getName(), out); + writeVersion(rootDesc.getVersion(), out); + } + + private void writeImportPackageSpec(ImportPackageSpecificationImpl importPackageSpec, DataOutputStream out) throws IOException { + if (writePrefix(importPackageSpec, out)) + return; + writeVersionConstraint(importPackageSpec, out); + // TODO this is a hack until the state dynamic loading is cleaned up + // we should only write the supplier if we are resolved + if (importPackageSpec.getBundle().isResolved()) + writeExportPackageDesc((ExportPackageDescriptionImpl) importPackageSpec.getSupplier(), out); + else + out.writeByte(StateReader.NULL); + + writeStringOrNull(importPackageSpec.getBundleSymbolicName(), out); + writeVersionRange(importPackageSpec.getBundleVersionRange(), out); + writeMap(out, importPackageSpec.getAttributes()); + writeMap(out, importPackageSpec.getDirectives()); + writeMap(out, importPackageSpec.getArbitraryDirectives()); + } + + private void writeHostSpec(HostSpecificationImpl host, DataOutputStream out, boolean force) throws IOException { + if (host != null && force && !forcedWrite.contains(host)) { + int index = addToObjectTable(host); + out.writeByte(StateReader.OBJECT); + out.writeInt(index); + forcedWrite.add(host); + } else if (writePrefix(host, out)) + return; + writeVersionConstraint(host, out); + BundleDescription[] hosts = host.getHosts(); + if (hosts == null) { + out.writeInt(0); + return; + } + out.writeInt(hosts.length); + for (int i = 0; i < hosts.length; i++) + writeBundleDescription(hosts[i], out, force); + writeMap(out, host.getAttributes()); + writeMap(out, host.getArbitraryDirectives()); + } + + // called by writers for VersionConstraintImpl subclasses + private void writeVersionConstraint(VersionConstraint constraint, DataOutputStream out) throws IOException { + writeStringOrNull(constraint.getName(), out); + writeVersionRange(constraint.getVersionRange(), out); + } + + private void writeVersion(Version version, DataOutputStream out) throws IOException { + if (version == null || version.equals(Version.emptyVersion)) { + out.writeByte(StateReader.NULL); + return; + } + out.writeByte(StateReader.OBJECT); + out.writeInt(version.getMajor()); + out.writeInt(version.getMinor()); + out.writeInt(version.getMicro()); + writeQualifier(version.getQualifier(), out); + } + + private void writeVersionRange(VersionRange versionRange, DataOutputStream out) throws IOException { + if (versionRange == null || versionRange.equals(VersionRange.emptyRange)) { + out.writeByte(StateReader.NULL); + return; + } + out.writeByte(StateReader.OBJECT); + writeVersion(versionRange.getMinimum(), out); + out.writeBoolean(versionRange.getIncludeMinimum()); + writeVersion(versionRange.getMaximum(), out); + out.writeBoolean(versionRange.getIncludeMaximum()); + } + + private boolean writeIndex(Object object, DataOutputStream out) throws IOException { + if (object == null) { + out.writeByte(StateReader.NULL); + return true; + } + int index = getFromObjectTable(object); + if (index == -1) + return false; + out.writeByte(StateReader.INDEX); + out.writeInt(index); + return true; + } + + public void saveStateDeprecated(StateImpl state, DataOutputStream output) throws IOException { + try { + writeStateDeprecated(state, output); + } finally { + output.close(); + } + } + + private void writeStringOrNull(String string, DataOutputStream out) throws IOException { + if (string == null) + out.writeByte(StateReader.NULL); + else { + byte[] data = string.getBytes(StateReader.UTF_8); + + if (data.length > 65535) { + out.writeByte(StateReader.LONG_STRING); + out.writeInt(data.length); + out.write(data); + } else { + out.writeByte(StateReader.OBJECT); + out.writeUTF(string); + } + } + } + + private void writeQualifier(String string, DataOutputStream out) throws IOException { + if (string != null && string.length() == 0) + string = null; + writeStringOrNull(string, out); + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/UserState.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/UserState.java new file mode 100644 index 000000000..905927941 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/UserState.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2003, 2010 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 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import java.util.*; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.BundleException; + +/** + * This implementation of State does a bookkeeping of all added/removed + */ +public class UserState extends StateImpl { + // TODO this is not an accurate way to record updates + private final Set<String> updated = Collections.synchronizedSet(new HashSet<String>()); + + public boolean removeBundle(BundleDescription description) { + if (description.getLocation() != null) + updated.remove(description.getLocation()); + if (!super.removeBundle(description)) + return false; + return true; + } + + public boolean updateBundle(BundleDescription newDescription) { + if (!super.updateBundle(newDescription)) + return false; + updated.add(newDescription.getLocation()); + return true; + } + + /** + * @throws BundleException + */ + public StateDelta compare(State baseState) throws BundleException { + BundleDescription[] current = this.getBundles(); + StateDeltaImpl delta = new StateDeltaImpl(this); + // process additions and updates + for (int i = 0; i < current.length; i++) { + BundleDescription existing = baseState.getBundleByLocation(current[i].getLocation()); + if (existing == null) + delta.recordBundleAdded((BundleDescriptionImpl) current[i]); + else if (updated.contains(current[i].getLocation())) + delta.recordBundleUpdated((BundleDescriptionImpl) current[i]); + } + // process removals + BundleDescription[] existing = baseState.getBundles(); + for (int i = 0; i < existing.length; i++) { + BundleDescription local = getBundleByLocation(existing[i].getLocation()); + if (local == null) + delta.recordBundleRemoved((BundleDescriptionImpl) existing[i]); + } + return delta; + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/VersionConstraintImpl.java b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/VersionConstraintImpl.java new file mode 100644 index 000000000..73682082f --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.state/src/org/eclipse/osgi/internal/resolver/VersionConstraintImpl.java @@ -0,0 +1,251 @@ +/******************************************************************************* + * Copyright (c) 2003, 2012 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 + * Danail Nachev - ProSyst - bug 218625 + * Rob Harrop - SpringSource Inc. (bug 247522) + *******************************************************************************/ +package org.eclipse.osgi.internal.resolver; + +import java.util.Collections; +import java.util.Map; + +import org.eclipse.osgi.internal.framework.EquinoxContainer; +import org.eclipse.osgi.internal.resolver.BaseDescriptionImpl.BaseCapability; +import org.eclipse.osgi.service.resolver.*; +import org.eclipse.osgi.util.ManifestElement; +import org.osgi.framework.Constants; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.namespace.AbstractWiringNamespace; +import org.osgi.framework.wiring.*; +import org.osgi.resource.Capability; +import org.osgi.resource.Namespace; + +abstract class VersionConstraintImpl implements VersionConstraint { + + protected final Object monitor = new Object(); + + private String name; + private VersionRange versionRange; + private BundleDescription bundle; + private BaseDescription supplier; + private volatile Object userObject; + + public String getName() { + synchronized (this.monitor) { + if (Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(name)) { + StateImpl state = (StateImpl) getBundle().getContainingState(); + return state == null ? EquinoxContainer.NAME : state.getSystemBundle(); + } + return name; + } + } + + public VersionRange getVersionRange() { + synchronized (this.monitor) { + if (versionRange == null) + return VersionRange.emptyRange; + return versionRange; + } + } + + public BundleDescription getBundle() { + synchronized (this.monitor) { + return bundle; + } + } + + public boolean isResolved() { + synchronized (this.monitor) { + return supplier != null; + } + } + + public BaseDescription getSupplier() { + synchronized (this.monitor) { + return supplier; + } + } + + public boolean isSatisfiedBy(BaseDescription candidate) { + synchronized (this.monitor) { + return false; + } + } + + protected void setName(String name) { + synchronized (this.monitor) { + this.name = name; + } + } + + protected void setVersionRange(VersionRange versionRange) { + synchronized (this.monitor) { + this.versionRange = versionRange; + } + } + + protected void setBundle(BundleDescription bundle) { + synchronized (this.monitor) { + this.bundle = bundle; + } + } + + protected void setSupplier(BaseDescription supplier) { + synchronized (this.monitor) { + this.supplier = supplier; + } + } + + protected abstract String getInternalNameSpace(); + + protected abstract Map<String, String> getInternalDirectives(); + + protected abstract Map<String, Object> getInteralAttributes(); + + protected abstract boolean hasMandatoryAttributes(String[] mandatory); + + public BundleRequirement getRequirement() { + String namespace = getInternalNameSpace(); + if (namespace == null) + return null; + return new BundleRequirementImpl(namespace); + } + + public Object getUserObject() { + return userObject; + } + + public void setUserObject(Object userObject) { + this.userObject = userObject; + } + + class BundleRequirementImpl implements BundleRequirement { + private final String namespace; + + public BundleRequirementImpl(String namespace) { + this.namespace = namespace; + } + + public String getNamespace() { + return namespace; + } + + @SuppressWarnings("unchecked") + public Map<String, String> getDirectives() { + return Collections.unmodifiableMap(getInternalDirectives()); + } + + @SuppressWarnings("unchecked") + public Map<String, Object> getAttributes() { + return Collections.unmodifiableMap(getInteralAttributes()); + } + + public BundleRevision getRevision() { + return getBundle(); + } + + public boolean matches(BundleCapability capability) { + return isSatisfiedBy(((BaseCapability) capability).getBaseDescription()); + } + + public int hashCode() { + return System.identityHashCode(VersionConstraintImpl.this); + } + + private VersionConstraintImpl getVersionConstraint() { + return VersionConstraintImpl.this; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof BundleRequirementImpl)) + return false; + return ((BundleRequirementImpl) obj).getVersionConstraint() == VersionConstraintImpl.this; + } + + public String toString() { + return getNamespace() + BaseDescriptionImpl.toString(getAttributes(), false) + BaseDescriptionImpl.toString(getDirectives(), true); + } + + public boolean matches(Capability capability) { + if (capability instanceof BundleCapability) + return matches((BundleCapability) capability); + // now we must do the generic thing + if (!namespace.equals(capability.getNamespace())) + return false; + String filterSpec = getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE); + try { + if (filterSpec != null && !FrameworkUtil.createFilter(filterSpec).matches(capability.getAttributes())) + return false; + } catch (InvalidSyntaxException e) { + return false; + } + return hasMandatoryAttributes(ManifestElement.getArrayFromList(capability.getDirectives().get(AbstractWiringNamespace.CAPABILITY_MANDATORY_DIRECTIVE))); + } + + public BundleRevision getResource() { + return getRevision(); + } + } + + static StringBuffer addFilterAttributes(StringBuffer filter, Map<String, ?> attributes) { + for (Map.Entry<String, ?> entry : attributes.entrySet()) { + addFilterAttribute(filter, entry.getKey(), entry.getValue()); + } + return filter; + } + + static StringBuffer addFilterAttribute(StringBuffer filter, String attr, Object value) { + return addFilterAttribute(filter, attr, value, true); + } + + static StringBuffer addFilterAttribute(StringBuffer filter, String attr, Object value, boolean escapeWildCard) { + if (value instanceof VersionRange) { + VersionRange range = (VersionRange) value; + filter.append(range.toFilterString(attr)); + } else { + filter.append('(').append(attr).append('=').append(escapeValue(value, escapeWildCard)).append(')'); + } + return filter; + } + + private static String escapeValue(Object o, boolean escapeWildCard) { + String value = o.toString(); + boolean escaped = false; + int inlen = value.length(); + int outlen = inlen << 1; /* inlen * 2 */ + + char[] output = new char[outlen]; + value.getChars(0, inlen, output, inlen); + + int cursor = 0; + for (int i = inlen; i < outlen; i++) { + char c = output[i]; + switch (c) { + case '*' : + if (!escapeWildCard) + break; + case '\\' : + case '(' : + case ')' : + output[cursor] = '\\'; + cursor++; + escaped = true; + break; + } + + output[cursor] = c; + cursor++; + } + + return escaped ? new String(output, 0, cursor) : value; + } +} |