blob: eb4756d22fa18e353adfb7141fa84d4b362e0d09 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2017 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
* Karsten Thoms <karsten.thoms@itemis.de> - Bug 522332
*******************************************************************************/
package org.eclipse.pde.internal.core;
import java.util.*;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.osgi.service.resolver.*;
import org.eclipse.pde.core.plugin.*;
import org.eclipse.pde.core.target.ITargetPlatformService;
import org.eclipse.pde.core.target.NameVersionDescriptor;
import org.osgi.framework.Constants;
/**
* Utility class to return bundle id collections for a variety of dependency
* scenarios
*
* @noextend This class is not intended to be subclassed by clients.
* @noinstantiate This class is not intended to be instantiated by clients.
*/
public class DependencyManager {
/**
* Returns a {@link Set} of bundle ids for the dependents of the given
* {@link IPluginModelBase}. The set includes the id of the given model base
* as well as all computed implicit / optional dependencies.
*
* @param model the {@link IPluginModelBase} to compute dependencies for
* @param excludeFragments a collection of <b>fragment</b> bundle symbolic names to exclude from the dependency resolution
* or <code>null</code> if none
* @return a set of bundle IDs
*/
public static Set<String> getSelfAndDependencies(IPluginModelBase model, String[] excludeFragments) {
return getDependencies(new Object[] {model}, getImplicitDependencies(), TargetPlatformHelper.getState(), false, true, toSet(excludeFragments));
}
/**
* Returns a {@link Set} of bundle ids for the dependents of the given
* {@link IPluginModelBase}s. The set includes the ids of the given model bases
* as well as all computed implicit / optional dependencies.
*
* @param models the array of {@link IPluginModelBase}s to compute dependencies for
* @param excludeFragments a collection of <b>fragment</b> bundle symbolic names to exclude from the dependency resolution
* or <code>null</code> if none
* @return a set of bundle IDs
*/
public static Set<String> getSelfandDependencies(IPluginModelBase[] models, String[] excludeFragments) {
return getDependencies(models, getImplicitDependencies(), TargetPlatformHelper.getState(), false, true, toSet(excludeFragments));
}
/**
* Returns a {@link Set} of bundle ids for the dependents of the given
* objects from the given {@link State}.
* The set does not include the ids of the given objects
* and only includes the given set of implicit dependencies.
*
* @param selected the group of objects to compute dependencies for. Any items
* in this array that are not {@link IPluginModelBase}s are ignored.
* @param implicit the array of additional implicit dependencies to add to the {@link Set}
* @param state the {@link State} to compute the dependencies in
* @param excludeFragments a collection of <b>fragment</b> bundle symbolic names to exclude from the dependency resolution
* or <code>null</code> if none
* @return a set of bundle IDs
*/
public static Set<String> getDependencies(Object[] selected, String[] implicit, State state, String[] excludeFragments) {
return getDependencies(selected, implicit, state, true, true, toSet(excludeFragments));
}
/**
* Returns a {@link Set} of bundle ids for the dependents of the given
* objects. The set does not include the ids of the given objects
* but does include the computed set of implicit dependencies.
*
* @param selected selected the group of objects to compute dependencies for. Any items
* in this array that are not {@link IPluginModelBase}s are ignored.
* @param includeOptional if optional bundle ids should be included
* @param excludeFragments a collection of <b>fragment</b> bundle symbolic names to exclude from the dependency resolution
* or <code>null</code> if none
* @return a set of bundle IDs
*/
public static Set<String> getDependencies(Object[] selected, boolean includeOptional, String[] excludeFragments) {
return getDependencies(selected, getImplicitDependencies(), TargetPlatformHelper.getState(), true, includeOptional, toSet(excludeFragments));
}
/**
* Returns the array as a set or <code>null</code>
* @param array array or <code>null</code>
* @return set
*/
private static Set<String> toSet(String[] array) {
Set<String> set = new HashSet<>();
if (array != null) {
for (String element : array) {
set.add(element);
}
}
return set;
}
/**
* Returns a {@link Set} of bundle ids for the dependents of the given objects
* from the given {@link State}. The set additionally only includes the given
* set of implicit dependencies.
*
* @param selected
* selected the group of objects to compute dependencies for. Any
* items in this array that are not {@link IPluginModelBase}s are
* ignored.
* @param implicit
* the array of additional implicit dependencies to add to the
* {@link Set}
* @param state
* the {@link State} to compute the dependencies in
* @param removeSelf
* if the id of one of the bundles were are computing dependencies
* for should be included in the result {@link Set} or not
* @param includeOptional
* if optional bundle ids should be included
* @param excludeFragments
* a collection of <b>fragment</b> bundle symbolic names to exclude
* from the dependency resolution
* @return a set of bundle IDs
*/
private static Set<String> getDependencies(Object[] selected, String[] implicit, State state, boolean removeSelf,
boolean includeOptional, Set<String> excludeFragments) {
Set<String> set = new TreeSet<>();
Set<IPluginModelBase> models = new HashSet<>();
// For all selected bundles add their bundle dependencies.
// Also consider plugin extensions and their dependencies.
for (int i = 0; i < selected.length; i++) {
if (!(selected[i] instanceof IPluginModelBase))
continue;
IPluginModelBase model = (IPluginModelBase) selected[i];
models.add(model);
addBundleAndDependencies(model.getBundleDescription(), set, includeOptional, excludeFragments);
IPluginExtension[] extensions = model.getPluginBase().getExtensions();
for (IPluginExtension extension : extensions) {
// TODO: this loop might be useless, because dependencies are already defined in
// the manifest
String point = extension.getPoint();
if (point != null) {
int dot = point.lastIndexOf('.');
if (dot != -1) {
String id = point.substring(0, dot);
addBundleAndDependencies(state.getBundle(id, null), set, includeOptional, excludeFragments);
}
}
}
}
for (String element : implicit) {
addBundleAndDependencies(state.getBundle(element, null), set, includeOptional, excludeFragments);
}
if (removeSelf) {
for (int i = 0; i < selected.length; i++) {
if (!(selected[i] instanceof IPluginModelBase)) {
continue;
}
IPluginModelBase model = (IPluginModelBase) selected[i];
set.remove(model.getPluginBase().getId());
}
}
if (!set.isEmpty()) {
// validate all models and try to add bundles that resolve constraint violations
for (IPluginModelBase model : DependencyManager.getDependencies(TargetPlatformHelper.getState(),
models.toArray(new IPluginModelBase[models.size()]))) {
set.add(model.getBundleDescription().getSymbolicName());
}
// build array with all selected plus calculated dependencies and recurse
// loop ends when no more additional dependencies are calculated
for (String id : set) {
ModelEntry entry = PluginRegistry.findEntry(id);
if (entry != null) {
models.add(entry.getModel());
}
}
Set<String> additionalIds = getDependencies(models.toArray(), implicit, state, removeSelf, includeOptional,
excludeFragments);
set.addAll(additionalIds);
}
return set;
}
/**
* Computes the set of implicit dependencies from the {@link PDEPreferencesManager}
* @return a set if bundle ids
*/
private static String[] getImplicitDependencies() {
try {
ITargetPlatformService service = PDECore.getDefault().acquireService(ITargetPlatformService.class);
if (service != null) {
NameVersionDescriptor[] implicit = service.getWorkspaceTargetDefinition().getImplicitDependencies();
if (implicit != null) {
String[] result = new String[implicit.length];
for (int i = 0; i < implicit.length; i++) {
result[i] = implicit[i].getId();
}
return result;
}
}
} catch (CoreException e) {
PDECore.log(e);
}
return new String[0];
}
/**
* Recursively adds the given {@link BundleDescription} and its dependents to the given
* {@link Set}
* @param desc the {@link BundleDescription} to compute dependencies for
* @param set the {@link Set} to collect results in
* @param includeOptional if optional dependencies should be included
* @param excludeFragments a collection of <b>fragment</b> bundle symbolic names to exclude from the dependency resolution
*/
private static void addBundleAndDependencies(BundleDescription desc, Set<String> set, boolean includeOptional, Set<String> excludeFragments) {
if (desc != null && set.add(desc.getSymbolicName())) {
BundleSpecification[] required = desc.getRequiredBundles();
for (int i = 0; i < required.length; i++) {
if (includeOptional || !required[i].isOptional()) {
addBundleAndDependencies((BundleDescription) required[i].getSupplier(), set, includeOptional, excludeFragments);
}
}
ImportPackageSpecification[] importedPkgs = desc.getImportPackages();
for (ImportPackageSpecification importedPkg : importedPkgs) {
ExportPackageDescription exporter = (ExportPackageDescription) importedPkg.getSupplier();
// Continue if the Imported Package is unresolved of the package is optional and don't want optional packages
if (exporter == null || (!includeOptional && Constants.RESOLUTION_OPTIONAL.equals(importedPkg.getDirective(Constants.RESOLUTION_DIRECTIVE)))) {
continue;
}
addBundleAndDependencies(exporter.getExporter(), set, includeOptional, excludeFragments);
}
BundleDescription[] fragments = desc.getFragments();
for (int i = 0; i < fragments.length; i++) {
if (!fragments[i].isResolved()) {
continue;
}
String id = fragments[i].getSymbolicName();
if (!excludeFragments.contains(id)) {
addBundleAndDependencies(fragments[i], set, includeOptional, excludeFragments);
}
}
HostSpecification host = desc.getHost();
if (host != null) {
addBundleAndDependencies((BundleDescription) host.getSupplier(), set, includeOptional, excludeFragments);
}
}
}
/**
* Validates the given models and retrieves bundle IDs that satisfy violated
* constraints. This method uses the {@link BundleValidationOperation} to
* determine unsatisfied constraints for the given plugin models.
*
* @param state
* the {@link State} to compute the dependencies in
* @param models
* the array of {@link IPluginModelBase}s to compute dependencies for
*
* @return a set of bundle IDs
*/
private static Set<IPluginModelBase> getDependencies(State state, IPluginModelBase[] models) {
Set<IPluginModelBase> dependencies = new HashSet<>();
BundleValidationOperation operation = new BundleValidationOperation(models);
try {
operation.run(new NullProgressMonitor());
Map<Object, Object[]> input = operation.getResolverErrors();
// extract the unsatisfied constraints from the operation's result structure
VersionConstraint[] unsatisfiedConstraints = input.values().stream()
.filter(ResolverError[].class::isInstance)
.map(ResolverError[].class::cast)
.flatMap(arr -> Arrays.stream(arr))
.filter(err -> err.getUnsatisfiedConstraint() != null)
.map(err -> err.getUnsatisfiedConstraint())
.toArray(VersionConstraint[]::new);
for (VersionConstraint constraint : unsatisfiedConstraints) {
// first try to find a solution in the set of additionally computed
// bundles that satisfy constraints.
if (dependencies.stream()
.anyMatch(pmb -> satisfiesConstraint(pmb.getBundleDescription(), constraint))) {
continue;
}
// determine all bundles from the target platform state that satisfy the current
// constraint
List<BundleDescription> satisfyingBundles = Arrays.stream(state.getBundles())
.filter(desc -> satisfiesConstraint(desc, constraint)).collect(Collectors.toList());
// It is possible to have none, exactly one, or in rare cases multiple bundles
// that satisfy the constraint.
for (BundleDescription bundle : satisfyingBundles) {
ModelEntry entry = PluginRegistry.findEntry(bundle.getSymbolicName());
if (entry != null) {
dependencies.add(entry.getModel());
}
}
}
return dependencies;
} catch (CoreException e) {
PDECore.log(e);
return Collections.emptySet();
}
}
private static boolean satisfiesConstraint(BundleDescription desc, VersionConstraint constraint) {
if (constraint instanceof GenericSpecification) {
for (GenericDescription description : desc.getGenericCapabilities()) {
if (constraint.isSatisfiedBy(description)) {
return true;
}
}
} else if (constraint instanceof BundleSpecification) {
return constraint.getName().equals(desc.getName())
&& constraint.getVersionRange().isIncluded(desc.getVersion());
}
return false;
}
}