blob: a5c5910f54d9a18a6422aabfb57b300f04641cdb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.internal.resolver;
import java.util.*;
import org.eclipse.osgi.framework.internal.core.Constants;
import org.eclipse.osgi.service.resolver.*;
/**
* 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 class StateHelperImpl implements StateHelper {
private static StateHelper instance = new StateHelperImpl();
/**
* @see StateHelper
*/
public BundleDescription[] getDependentBundles(BundleDescription[] bundles) {
if (bundles == null || bundles.length == 0)
return new BundleDescription[0];
Set reachable = new HashSet(bundles.length);
for (int i = 0; i < bundles.length; i++) {
if (!bundles[i].isResolved())
continue;
addDependentBundles(bundles[i], reachable);
}
return (BundleDescription[]) reachable.toArray(new BundleDescription[reachable.size()]);
}
private void addDependentBundles(BundleDescription bundle, Set 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 reachable = new HashSet(bundles.length);
for (int i = 0; i < bundles.length; i++)
addPrerequisites(bundles[i], reachable);
return (BundleDescription[]) reachable.toArray(new BundleDescription[reachable.size()]);
}
private void addPrerequisites(BundleDescription bundle, Set reachable) {
if (reachable.contains(bundle))
return;
reachable.add(bundle);
List depList = ((BundleDescriptionImpl) bundle).getBundleDependencies();
BundleDescription[] dependencies = (BundleDescription[]) depList.toArray(new BundleDescription[depList.size()]);
for (int i = 0; i < dependencies.length; i++)
addPrerequisites(dependencies[i], reachable);
}
private Map getExportedPackageMap(State state) {
Map result = new HashMap(11);
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];
Set exports = (Set) result.get(description.getName());
if (exports == null) {
exports = new HashSet(1);
result.put(description.getName(), exports);
}
exports.add(description);
}
}
return result;
}
private Map getGenericsMap(State state, boolean resolved) {
Map result = new HashMap(11);
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];
Set genericSet = (Set) result.get(description.getName());
if (genericSet == null) {
genericSet = new HashSet(1);
result.put(description.getName(), genericSet);
}
genericSet.add(description);
}
}
return result;
}
private VersionConstraint[] getUnsatisfiedLeaves(State state, BundleDescription[] bundles) {
Map packages = getExportedPackageMap(state);
Map generics = getGenericsMap(state, false);
HashSet result = new HashSet(11);
for (int i = 0; i < bundles.length; i++) {
BundleDescription description = bundles[i];
VersionConstraint[] constraints = getUnsatisfiedConstraints(description);
for (int j = 0; j < constraints.length; j++) {
VersionConstraint constraint = constraints[j];
boolean satisfied = false;
if (constraint instanceof BundleSpecification || constraint instanceof HostSpecification) {
BundleDescription[] suppliers = state.getBundles(constraint.getName());
for (int k = 0; k < suppliers.length && !satisfied; k++)
satisfied |= constraint.isSatisfiedBy(suppliers[k]);
} else if (constraint instanceof ImportPackageSpecification) {
Set exports = (Set) packages.get(constraint.getName());
if (exports != null)
for (Iterator iter = exports.iterator(); iter.hasNext() && !satisfied;)
satisfied |= constraint.isSatisfiedBy((ExportPackageDescription) iter.next());
} else if (constraint instanceof GenericSpecification) {
Set genericSet = (Set) generics.get(constraint.getName());
if (genericSet != null)
for (Iterator iter = genericSet.iterator(); iter.hasNext() && !satisfied;)
satisfied |= constraint.isSatisfiedBy((GenericDescription) iter.next());
}
if (!satisfied)
result.add(constraint);
}
}
return (VersionConstraint[]) result.toArray(new VersionConstraint[result.size()]);
}
public VersionConstraint[] getUnsatisfiedLeaves(BundleDescription[] bundles) {
if (bundles.length == 0)
return new VersionConstraint[0];
State state = bundles[0].getContainingState();
return getUnsatisfiedLeaves(state, bundles);
}
/**
* @see StateHelper
*/
public VersionConstraint[] getUnsatisfiedConstraints(BundleDescription bundle) {
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 unsatisfied = new ArrayList();
HostSpecification host = bundle.getHost();
if (host != null)
if (!host.isResolved() && !isResolvable(host))
unsatisfied.add(host);
BundleSpecification[] requiredBundles = bundle.getRequiredBundles();
for (int i = 0; i < requiredBundles.length; i++)
if (!requiredBundles[i].isResolved() && !isResolvable(requiredBundles[i]))
unsatisfied.add(requiredBundles[i]);
ImportPackageSpecification[] packages = bundle.getImportPackages();
for (int i = 0; i < packages.length; i++)
if (!packages[i].isResolved() && !isResolvable(packages[i]))
unsatisfied.add(packages[i]);
GenericSpecification[] generics = bundle.getGenericRequires();
for (int i = 0; i < generics.length; i++)
if (!generics[i].isResolved() && !isResolvable(generics[i]))
unsatisfied.add(generics[i]);
return (VersionConstraint[]) unsatisfied.toArray(new VersionConstraint[unsatisfied.size()]);
}
/**
* @see StateHelper
*/
public boolean isResolvable(ImportPackageSpecification constraint) {
ExportPackageDescription[] exports = constraint.getBundle().getContainingState().getExportedPackages();
for (int i = 0; i < exports.length; i++)
if (constraint.isSatisfiedBy(exports[i]))
return true;
return false;
}
private boolean isResolvable(GenericSpecification constraint) {
Map genericCapabilities = getGenericsMap(constraint.getBundle().getContainingState(), true);
Set genericSet = (Set) genericCapabilities.get(constraint.getName());
if (genericSet == null)
return false;
for (Iterator iter = genericSet.iterator(); iter.hasNext();)
if (constraint.isSatisfiedBy((GenericDescription) iter.next()))
return true;
return false;
}
/**
* @see StateHelper
*/
public boolean isResolvable(BundleSpecification specification) {
return isBundleConstraintResolvable(specification);
}
/**
* @see StateHelper
*/
public boolean isResolvable(HostSpecification specification) {
return isBundleConstraintResolvable(specification);
}
/*
* Returns whether a bundle specification/host specification can be resolved.
*/
private boolean isBundleConstraintResolvable(VersionConstraint constraint) {
BundleDescription[] availableBundles = constraint.getBundle().getContainingState().getBundles(constraint.getName());
for (int i = 0; i < availableBundles.length; i++)
if (availableBundles[i].isResolved() && constraint.isSatisfiedBy(availableBundles[i]))
return true;
return false;
}
public Object[][] sortBundles(BundleDescription[] toSort) {
List references = new ArrayList(toSort.length);
for (int i = 0; i < toSort.length; i++)
if (toSort[i].isResolved())
buildReferences(toSort[i], references);
return ComputeNodeOrder.computeNodeOrder(toSort, (Object[][]) references.toArray(new Object[references.size()][]));
}
private void buildReferences(BundleDescription description, List references) {
HostSpecification host = description.getHost();
// it is a fragment
if (host != null) {
// just create a dependency between fragment and host
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]});
}
} else {
// it is a host
buildReferences(description, ((BundleDescriptionImpl) description).getBundleDependencies(), references);
}
}
private void buildReferences(BundleDescription description, List dependencies, List references) {
for (Iterator iter = dependencies.iterator(); iter.hasNext();)
addReference(description, (BundleDescription) iter.next(), references);
}
private void addReference(BundleDescription description, BundleDescription reference, List references) {
// build the reference from the description
if (description == reference || reference == null)
return;
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();
ArrayList orderedPkgList = new ArrayList(); // list of all ExportPackageDescriptions that are visible (ArrayList is used to keep order)
Set pkgSet = new HashSet();
Set importList = new HashSet(); // list of package names which are directly imported
// get the list of directly imported packages first.
ImportPackageSpecification[] imports = bundle.getImportPackages();
for (int i = 0; i < imports.length; i++) {
ExportPackageDescription pkgSupplier = (ExportPackageDescription) imports[i].getSupplier();
if (pkgSupplier == null)
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 visited = new HashSet();
visited.add(bundle); // always add self to prevent recursing into self
Set importNames = new HashSet(1);
importNames.add(imports[i].getName());
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[i].getName()); // besure to add to direct import list
}
// now find all the packages that are visible from required bundles
BundleSpecification[] requires = bundle.getRequiredBundles();
Set visited = new HashSet(requires.length);
visited.add(bundle); // always add self to prevent recursing into self
for (int i = 0; i < requires.length; i++) {
BundleDescription bundleSupplier = (BundleDescription) requires[i].getSupplier();
if (bundleSupplier != null)
getPackages(bundleSupplier, bundle.getSymbolicName(), importList, orderedPkgList, pkgSet, visited, strict, null, options);
}
return (ExportPackageDescription[]) orderedPkgList.toArray(new ExportPackageDescription[orderedPkgList.size()]);
}
private void getPackages(BundleDescription requiredBundle, String symbolicName, Set importList, ArrayList orderedPkgList, Set pkgSet, Set visited, boolean strict, Set 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[] exports = requiredBundle.getSelectedExports();
HashSet exportNames = new HashSet(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.
BundleSpecification[] requiredBundles = requiredBundle.getRequiredBundles();
for (int i = 0; i < requiredBundles.length; i++) {
if (requiredBundles[i].getSupplier() == null)
continue;
if (requiredBundles[i].isExported()) {
// looking for a specific package and that package is exported by this bundle or adding all packages from a reexported bundle
getPackages((BundleDescription) requiredBundles[i].getSupplier(), symbolicName, importList, orderedPkgList, pkgSet, visited, strict, pkgNames, options);
} else if (exportNames.size() > 0) {
// adding any exports from required bundles which we also export
Set tmpVisited = new HashSet();
getPackages((BundleDescription) requiredBundles[i].getSupplier(), 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(Constants.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(Constants.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;
}
}