/*******************************************************************************
* 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.importexpansion;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.osgi.framework.Version;
import org.eclipse.virgo.util.common.StringUtils;
import org.eclipse.virgo.util.osgi.manifest.VersionRange;
import org.eclipse.virgo.util.osgi.manifest.BundleManifest;
import org.eclipse.virgo.util.osgi.manifest.BundleManifestFactory;
import org.eclipse.virgo.util.osgi.manifest.ExportedPackage;
import org.eclipse.virgo.util.osgi.manifest.ImportedPackage;
/**
* Provides a number of helper methods for processing {@link BundleManifest BundleManifests}.
* <p />
*
* <strong>Concurrent Semantics</strong><br />
* Thread-safe
*
*/
class BundleManifestProcessor {
private static final String BUNDLE_SYMBOLIC_NAME_ATTRIBUTE_NAME = "bundle-symbolic-name";
private static final String BUNDLE_VERSION_ATTRIBUTE_NAME = "bundle-version";
private static final String VERSION_ATTRIBUTE_NAME = "version";
/**
* Creates a {@link ImportedPackage} for each of the supplied {@link ExportedPackage ExportedPackages} and returns a
* {@link Result} containing them. The result also contains any warnings that were generated during the processing
* of the manifests.
*
* @param packageExports the <code>ExportedPackages</code> to process
* @return a Result containing the package imports and any warnings
*/
static List<ImportedPackage> createImportedPackageForEachExportedPackage(List<ExportedPackage> packageExports, String bundleSymbolicName,
String bundleVersion) {
BundleManifest manifest = BundleManifestFactory.createBundleManifest();
doCreateImportedPackageForEachExportedPackage(packageExports, bundleSymbolicName, bundleVersion, null, manifest);
return manifest.getImportPackage().getImportedPackages();
}
/**
* Creates a {@link ImportedPackage} for each of the supplied {@link ExportedPackage ExportedPackages} and returns a
* {@link Result} containing them. The result also contains any warnings that were generated during the processing
* of the manifests.
*
* @param packageExports the <code>ExportedPackages</code> to process
* @return a Result containing the package imports and any warnings
*/
static List<ImportedPackage> createImportedPackageForEachExportedPackageOfFragment(List<ExportedPackage> packageExports,
String bundleSymbolicName, VersionRange bundleVersionRange) {
BundleManifest manifest = BundleManifestFactory.createBundleManifest();
doCreateImportedPackageForEachExportedPackage(packageExports, bundleSymbolicName, null, bundleVersionRange, manifest);
return manifest.getImportPackage().getImportedPackages();
}
/**
* Creates a {@link ImportedPackage} for each package export found in the given {@link BundleManifest
* BundleManifests} and returns a {@link Result} containing the package imports. The result also contains any
* warnings that were generated during the processing of the manifests.
*
* @param bundleManifests the bundle manifests to process
* @return a Result containing the package imports and any warnings
*/
static Result<ImportedPackage[]> createImportedPackageForEachExportedPackage(BundleManifest[] bundleManifests) {
List<ImportedPackage> allImportedPackages = new ArrayList<ImportedPackage>();
List<Warning> warnings = new ArrayList<Warning>();
for (BundleManifest bundleManifest : bundleManifests) {
List<ExportedPackage> packageExports = bundleManifest.getExportPackage().getExportedPackages();
BundleManifest resultManifest = BundleManifestFactory.createBundleManifest();
doCreateImportedPackageForEachExportedPackage(packageExports, null, null, null, resultManifest);
List<ImportedPackage> packageImports = resultManifest.getImportPackage().getImportedPackages();
for (ImportedPackage packageImport : packageImports) {
String name = packageImport.getPackageName();
if (!containsImport(allImportedPackages, name)) {
allImportedPackages.add(packageImport);
} else {
warnings.add(new Warning(Code.DUPLICATE_PACKAGE_EXPORTS, name));
}
}
}
return new Result<ImportedPackage[]>(allImportedPackages.toArray(new ImportedPackage[allImportedPackages.size()]),
warnings.toArray(new Warning[warnings.size()]));
}
private static void doCreateImportedPackageForEachExportedPackage(List<ExportedPackage> packageExports, String bundleSymbolicName,
String version, VersionRange versionRange, BundleManifest bundleManifest) {
for (ExportedPackage packageExport : packageExports) {
String name = packageExport.getPackageName();
List<ImportedPackage> packageImports = bundleManifest.getImportPackage().getImportedPackages();
ImportedPackage packageImport = findImport(packageImports, name);
if (packageImport == null) {
packageImport = bundleManifest.getImportPackage().addImportedPackage(name);
}
List<String> mandatory = packageExport.getMandatory();
Map<String, String> attributes = new HashMap<String, String>();
if (mandatory != null) {
for (String attrName : mandatory) {
if (packageExport.getAttributes().containsKey(attrName)) {
attributes.put(attrName, packageExport.getAttributes().get(attrName));
}
}
}
if (bundleSymbolicName != null) {
attributes.put(BUNDLE_SYMBOLIC_NAME_ATTRIBUTE_NAME, bundleSymbolicName);
}
if (StringUtils.hasText(version)) {
VersionRange vr;
try {
vr = VersionRange.createExactRange(new Version(version));
} catch (IllegalArgumentException e) {
vr = new VersionRange(version);
}
attributes.put(BUNDLE_VERSION_ATTRIBUTE_NAME, vr.toParseString());
}
/*
* If we are importing a fragment, use the supplied bundle version range and if this range is not exact, add
* an exact package version.
*/
if (versionRange != null) {
// If the bundle version range is not all possible versions, add it as an attribute.
if (!(versionRange.isFloorInclusive() && !versionRange.isCeilingInclusive() && versionRange.getFloor().equals(Version.emptyVersion))) {
attributes.put(BUNDLE_VERSION_ATTRIBUTE_NAME, versionRange.toParseString());
}
if (!versionRange.isExact()) {
VersionRange packageVersionRange = VersionRange.createExactRange(packageExport.getVersion());
attributes.put(VERSION_ATTRIBUTE_NAME, packageVersionRange.toParseString());
}
}
packageImport.getAttributes().putAll(attributes);
}
}
private static boolean containsImport(List<ImportedPackage> imports, String packageName) {
for (ImportedPackage packageImport : imports) {
if (packageImport.getPackageName().equals(packageName)) {
return true;
}
}
return false;
}
private static ImportedPackage findImport(List<ImportedPackage> imports, String packageName) {
for (ImportedPackage packageImport : imports) {
if (packageImport.getPackageName().equals(packageName)) {
return packageImport;
}
}
return null;
}
/**
* Encapsulates the output from bundle manifest processing processing along with any {@link Warning Warnings} that
* were generated during that processing.
* <p />
*
* <strong>Concurrent Semantics</strong><br />
* Thread-safe.
*
* @param <T> output type
*/
public static class Result<T> {
private final T output;
private final Warning[] warnings;
private Result(T output, Warning[] warnings) {
this.output = output;
this.warnings = warnings;
}
/**
* Returns the output of this result
*
* @return the output
*/
public T getOutput() {
return this.output;
}
/**
* Returns any warnings generated while producing the result. If no warnings were generated an empty array will
* be returned.
*
* @return any generated warnings
*/
public Warning[] getWarnings() {
return this.warnings.clone();
}
/**
* Returns whether this result was successful. A result is deemed to be successful if it does not contain any
* warnings
*
* @return <code>true</code> if the result is successful
*/
public boolean success() {
return this.warnings.length == 0;
}
}
/**
* A Warning describes a problem that was encountered during BundleManifestHelper processing
* <p />
*
* <strong>Concurrent Semantics</strong><br />
* Thread-safe.
*
*/
public static class Warning {
private final Code code;
private final String reason;
private Warning(Code code, String reason) {
this.code = code;
this.reason = reason;
}
/**
* Returns the {@link Code} that identifies the nature of this warning
*
* @return the warning's code
*/
public Code getCode() {
return this.code;
}
/**
* Returns the reason for the generation of this warning
*
* @return the warning's reason
*/
public String getReason() {
return this.reason;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object other) {
if (other == null) {
return false;
}
if (other == this) {
return true;
}
if (!(other instanceof Warning)) {
return false;
}
Warning that = (Warning) other;
return this.getCode().equals(that.getCode()) && this.getReason().equals(that.getReason());
}
@Override
public int hashCode() {
return this.code.hashCode();
}
}
/**
* Defines a unique code for each of the warnings that may be generated as a result of bundle manifest processing.
* <p />
* <strong>Concurrent Semantics</strong><br />
* Thread-safe.
*
*/
public enum Code {
/**
* More than one of the input {@link BundleManifest BundleManifests} exported the same package.
*/
DUPLICATE_PACKAGE_EXPORTS;
}
}