/*******************************************************************************
* Copyright (c) 2008, 2010 VMware Inc.
* 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:
* VMware Inc. - initial contribution
*******************************************************************************/
package org.eclipse.virgo.kernel.userregion.internal.equinox;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.service.resolver.ExportPackageDescription;
import org.eclipse.osgi.service.resolver.ImportPackageSpecification;
import org.eclipse.osgi.service.resolver.ResolverError;
import org.eclipse.osgi.service.resolver.State;
import org.eclipse.osgi.service.resolver.VersionConstraint;
import org.osgi.framework.Constants;
import org.eclipse.virgo.util.math.Sets;
/**
* Utility class for analysing uses failures in a given bundle.
*
* <strong>Concurrent Semantics</strong><br/>
* thread-safe
*
*/
public final class UsesAnalyser {
public AnalysedUsesConflict[] getUsesConflicts(State state, ResolverError usesError) {
VersionConstraint constraint = usesError.getUnsatisfiedConstraint();
List<AnalysedUsesConflict> analysedUsesConflicts = new ArrayList<AnalysedUsesConflict>();
if (constraint instanceof ImportPackageSpecification) {
ImportPackageSpecification rootImport = (ImportPackageSpecification) constraint;
/*
* Compute the exports visible from the failed bundle except via rootImport. This is the exports matching
* imports (except rootImport) of the failed bundle and the uses constraint closure of those exports. The
* uses constraint closure of an export is those packages which are visible through transitive uses starting
* with the export.
*/
PackageSources visiblePackages = generateExportPackagesVisibleInFailedBundle(state, rootImport);
/*
* For each resolved export that satisfies rootImport, compute the uses constraint closure of the export and
* add any that conflict with visiblePackages to the resultant set of uses conflicts.
*/
for (ExportPackageDescription exportPackage : getResolvedCandidateExports(state, rootImport)) {
PackageSources usedPackages = generateExportPackagesUsedViaExportPackage(state, exportPackage);
analysedUsesConflicts.addAll(findConflictingExports(usedPackages, visiblePackages));
}
if (analysedUsesConflicts.isEmpty()) {
// Be more aggressive by exploring unresolved exports that satisfy rootImport.
for (ExportPackageDescription exportPackage : getUnresolvedCandidateExports(state, rootImport)) {
PackageSources usedPackages = generateExportPackagesUsedViaExportPackage(state, exportPackage);
analysedUsesConflicts.addAll(findConflictingExports(usedPackages, visiblePackages));
}
}
}
return analysedUsesConflicts.toArray(new AnalysedUsesConflict[analysedUsesConflicts.size()]);
}
public ResolverError[] getUsesResolverErrors(State state, BundleDescription bundle) {
ResolverError[] errors = state.getResolverErrors(bundle);
if (errors != null) {
List<ResolverError> usesErrors = new ArrayList<ResolverError>(errors.length);
for (ResolverError re : errors) {
if (re.getType() == ResolverError.IMPORT_PACKAGE_USES_CONFLICT) {
usesErrors.add(re);
}
}
return usesErrors.toArray(new ResolverError[usesErrors.size()]);
}
return null;
}
private List<AnalysedUsesConflict> findConflictingExports(PackageSources usedPackages, PackageSources directPackages) {
List<AnalysedUsesConflict> usesConflicts = new ArrayList<AnalysedUsesConflict>();
Set<String> packagesInCommon = Sets.<String> intersection(usedPackages.keySet(), directPackages.keySet());
for (String packageName : packagesInCommon) {
Set<SourcedPackage> allUsed = usedPackages.get(packageName);
Set<SourcedPackage> allWired = directPackages.get(packageName);
for (SourcedPackage sourcedPackage : allUsed) {
UsedBySourcedPackage usedSourced = (UsedBySourcedPackage) sourcedPackage;
if (!exportDescriptionOccursIn(usedSourced.getSource(), allWired)) {
for (SourcedPackage sp : allWired) {
usesConflicts.add(new AnalysedUsesConflict(usedSourced, sp));
}
}
}
}
return usesConflicts;
}
private static final boolean exportDescriptionOccursIn(ExportPackageDescription source, Set<SourcedPackage> allWired) {
for (SourcedPackage w : allWired) {
if (w.getSource().equals(source))
return true;
}
return false;
}
private PackageSources generateExportPackagesUsedViaExportPackage(State state, ExportPackageDescription exportPackage) {
PackageSources usedPackages = constructEmptyPackageSources();
Set<String> knownPackages = new HashSet<String>();
addUsedImportedPackages(state, usedPackages, exportPackage, exportPackage, knownPackages);
return usedPackages;
}
private PackageSources generateExportPackagesVisibleInFailedBundle(State state, ImportPackageSpecification rootImport) {
PackageSources visiblePackages = getOtherImportedPackages(state, rootImport);
visiblePackages.putAll(computeUsesClosure(state, visiblePackages));
BundleDescription failedBundle = rootImport.getBundle();
visiblePackages.putAll(getExportedPackages(failedBundle));
return visiblePackages;
}
private PackageSources computeUsesClosure(State state, PackageSources packages) {
// Compute all the exports visible through transitive uses from directPackages
PackageSources additionalPackages = constructEmptyPackageSources();
Set<Entry<String, Set<SourcedPackage>>> keys = packages.entrySet();
Set<String> knownPackages = new HashSet<String>();
for (Entry<String, Set<SourcedPackage>> key : keys) {
for (SourcedPackage sp : key.getValue()) {
ExportPackageDescription source = sp.getSource();
addUsedImportedPackages(state, additionalPackages, source, source, knownPackages);
}
}
return additionalPackages;
}
private ExportPackageDescription[] getResolvedCandidateExports(State state, ImportPackageSpecification rootImport) {
List<ExportPackageDescription> exports = new ArrayList<ExportPackageDescription>();
BundleDescription[] bundles = state.getResolvedBundles();
if (bundles != null) {
for (BundleDescription bundle : bundles) {
for (ExportPackageDescription exportPackage : bundle.getExportPackages()) {
if (rootImport.isSatisfiedBy(exportPackage)) {
exports.add(exportPackage);
}
}
}
}
return exports.toArray(new ExportPackageDescription[exports.size()]);
}
private ExportPackageDescription[] getUnresolvedCandidateExports(State state, ImportPackageSpecification rootImport) {
List<ExportPackageDescription> exports = new ArrayList<ExportPackageDescription>();
BundleDescription[] resolvedBundles = state.getResolvedBundles();
BundleDescription[] bundles = state.getBundles();
if (bundles != null) {
for (BundleDescription bundle : bundles) {
if (notInArray(bundle, resolvedBundles)) {
for (ExportPackageDescription exportPackage : bundle.getExportPackages()) {
if (rootImport.isSatisfiedBy(exportPackage)) {
exports.add(exportPackage);
}
}
}
}
}
return exports.toArray(new ExportPackageDescription[exports.size()]);
}
private static final boolean notInArray(BundleDescription bundle, BundleDescription[] resolvedBundles) {
if (resolvedBundles == null)
return true;
for (BundleDescription b : resolvedBundles) {
if (b == bundle) {
return false;
}
}
return true;
}
private void addUsedImportedPackages(State state, PackageSources packages, ExportPackageDescription exportPackage,
ExportPackageDescription topDependency, Set<String> knownPackages) {
String[] packageNames = (String[]) exportPackage.getDirective(Constants.USES_DIRECTIVE);
if (packageNames != null) {
BundleDescription bundle = exportPackage.getExporter();
ExportPackageDescription[] allExports = bundle.getExportPackages();
ImportPackageSpecification[] allImports = bundle.getImportPackages();
ExportPackageDescription[] allResolvedImports = bundle.getResolvedImports();
for (String packageName : packageNames) {
ExportPackageDescription localExport = findExportPackageDescriptionInArray(allExports, packageName);
if (null != localExport) {
packages.addPackageSource(packageName, new UsedBySourcedPackage(topDependency, localExport));
}
if (!knownPackages.contains(packageName)) {
ExportPackageDescription localResolvedImport = findExportPackageDescriptionInArray(allResolvedImports, packageName);
if (null != localResolvedImport) {
knownPackages.add(packageName);
packages.addPackageSource(packageName, new UsedBySourcedPackage(topDependency, localResolvedImport));
addUsedImportedPackages(state, packages, localResolvedImport, topDependency, knownPackages);
} else {
ImportPackageSpecification anImport = findImportPackageSpecificationInArray(allImports, packageName);
if (anImport != null) {
ExportPackageDescription[] matchingExports = getCandidateExports(state, anImport);
if (matchingExports.length != 0) {
knownPackages.add(packageName);
for (ExportPackageDescription matchingExport : matchingExports) {
packages.addPackageSource(packageName, new UsedBySourcedPackage(topDependency, matchingExport));
addUsedImportedPackages(state, packages, matchingExport, topDependency, knownPackages);
}
}
}
}
}
}
}
}
private static final ImportPackageSpecification findImportPackageSpecificationInArray(ImportPackageSpecification[] allImports, String packageName) {
for (ImportPackageSpecification ips : allImports) {
if (packageName.equals(ips.getName())) {
return ips;
}
}
return null;
}
private static final ExportPackageDescription findExportPackageDescriptionInArray(ExportPackageDescription[] allExports, String packageName) {
for (ExportPackageDescription epd : allExports) {
if (packageName.equals(epd.getName())) {
return epd;
}
}
return null;
}
private PackageSources getOtherImportedPackages(State state, ImportPackageSpecification rootImport) {
BundleDescription bundle = rootImport.getBundle();
PackageSources packages = constructEmptyPackageSources();
ImportPackageSpecification[] importSpecifications = bundle.getImportPackages();
for (ImportPackageSpecification importSpecification : importSpecifications) {
if (rootImport != importSpecification) {
if (!Constants.RESOLUTION_OPTIONAL.equals(importSpecification.getDirective(Constants.RESOLUTION_DIRECTIVE))) {
ExportPackageDescription[] exportPackages = getCandidateExports(state, importSpecification);
for (ExportPackageDescription exportPackage : exportPackages) {
packages.addPackageSource(exportPackage.getName(), new ImportedSourcedPackage(rootImport, exportPackage));
}
}
}
}
return packages;
}
private ExportPackageDescription[] getCandidateExports(State state, ImportPackageSpecification importSpecification) {
ExportPackageDescription[] pkgs = getResolvedCandidateExports(state, importSpecification);
if (pkgs.length == 0)
pkgs = getUnresolvedCandidateExports(state, importSpecification);
return pkgs;
}
private PackageSources getExportedPackages(BundleDescription bundle) {
ExportPackageDescription[] packageArray = bundle.getExportPackages();
PackageSources packages = constructEmptyPackageSources();
if (packageArray != null)
for (ExportPackageDescription exportPackage : packageArray) {
packages.addPackageSource(exportPackage.getName(), new SourcedPackage(exportPackage));
}
return packages;
}
private final static String stringOf(ExportPackageDescription source) {
BundleDescription bundle = source.getSupplier();
StringBuilder sb = new StringBuilder("'");
sb.append(source.getName()).append("_").append(source.getVersion()).append("' in bundle ").append(stringOf(bundle));
return sb.toString();
}
private final static String stringOf(BundleDescription bundle) {
StringBuilder sb = new StringBuilder("'");
sb.append(bundle.getSymbolicName()).append("_").append(bundle.getVersion()).append("[").append(bundle.getBundleId()).append("]").append("'");
return sb.toString();
}
public static interface UsesViolation {
VersionConstraint getConstraint();
PossibleMatch[] getPossibleMatches();
}
public static interface PossibleMatch {
ExportPackageDescription getSupplier();
boolean isDependentConstraintMismatch();
DependentConstraintCollision[] getCollisions();
}
public static interface DependentConstraintCollision {
ImportPackageSpecification getConsumerConstraint();
ImportPackageSpecification getSupplierConstraint();
CollisionReason getCollisionReason();
}
public static enum CollisionReason {
DISJOINT_VERSION_RANGES, ATTRIBUTE_MISMATCH
}
private static class SourcedPackage {
private final ExportPackageDescription source;
public SourcedPackage(ExportPackageDescription source) {
this.source = source;
}
public String toString() {
return stringOf(this.source);
}
public ExportPackageDescription getSource() {
return this.source;
}
}
private static final class ImportedSourcedPackage extends SourcedPackage {
private final ImportPackageSpecification rootImport;
public ImportedSourcedPackage(ImportPackageSpecification rootImport, ExportPackageDescription source) {
super(source);
this.rootImport = rootImport;
}
public String toString() {
StringBuilder sb = new StringBuilder(super.toString());
sb.append(" imported by bundle ").append(stringOf(this.rootImport.getBundle()));
return sb.toString();
}
}
private static final class UsedBySourcedPackage extends SourcedPackage {
private final ExportPackageDescription usedBy;
public UsedBySourcedPackage(ExportPackageDescription usedBy, ExportPackageDescription source) {
super(source);
this.usedBy = usedBy;
}
public String toString() {
StringBuilder sb = new StringBuilder(super.toString());
sb.append(" used by ").append(stringOf(this.usedBy));
return sb.toString();
}
public ExportPackageDescription getUsedBy() {
return this.usedBy;
}
}
private interface PackageSources extends Map<String, Set<SourcedPackage>> {
public void addPackageSource(String packageName, SourcedPackage sourcedPackage);
}
private class PackageSourcesImpl extends HashMap<String, Set<SourcedPackage>> implements PackageSources {
private static final long serialVersionUID = 1L;
public final void addPackageSource(String packageName, SourcedPackage sourcedPackage) {
Set<SourcedPackage> sourcedSet = get(packageName);
if (sourcedSet == null) {
sourcedSet = new HashSet<SourcedPackage>();
put(packageName, sourcedSet);
}
sourcedSet.add(sourcedPackage);
}
}
private PackageSources constructEmptyPackageSources() {
return new PackageSourcesImpl();
}
public static final class AnalysedUsesConflict {
private final UsedBySourcedPackage usedPackage;
private final SourcedPackage resolvedPackage;
private AnalysedUsesConflict(UsedBySourcedPackage used, SourcedPackage resolved) {
this.usedPackage = used;
this.resolvedPackage = resolved;
}
public String[] getConflictStatement() {
return new String[] { "package " + this.usedPackage.toString(), "conflicts with " + this.resolvedPackage.toString() };
}
public ExportPackageDescription getConflictingPackage() {
return this.usedPackage == null ? null : this.usedPackage.getSource();
}
public ExportPackageDescription getUsesRootPackage() {
return this.usedPackage == null ? null : this.usedPackage.getUsedBy();
}
public ExportPackageDescription getPackage() {
return this.resolvedPackage == null ? null : this.resolvedPackage.getSource();
}
}
}