All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.osgi.container.builders.OSGiManifestBuilderFactory Maven / Gradle / Ivy

Go to download

AspectJ tools most notably contains the AspectJ compiler (AJC). AJC applies aspects to Java classes during compilation, fully replacing Javac for plain Java classes and also compiling native AspectJ or annotation-based @AspectJ syntax. Furthermore, AJC can weave aspects into existing class files in a post-compile binary weaving step. This library is a superset of AspectJ weaver and hence also of AspectJ runtime.

There is a newer version: 1.9.22.1
Show newest version
/*******************************************************************************
 * Copyright (c) 2012, 2021 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.osgi.container.builders;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.osgi.container.ModuleRevisionBuilder;
import org.eclipse.osgi.container.namespaces.EclipsePlatformNamespace;
import org.eclipse.osgi.container.namespaces.EquinoxFragmentNamespace;
import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace;
import org.eclipse.osgi.internal.framework.EquinoxContainer;
import org.eclipse.osgi.internal.framework.FilterImpl;
import org.eclipse.osgi.internal.messages.Msg;
import org.eclipse.osgi.internal.util.Tokenizer;
import org.eclipse.osgi.storage.NativeCodeFinder;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.Version;
import org.osgi.framework.VersionRange;
import org.osgi.framework.namespace.BundleNamespace;
import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
import org.osgi.framework.namespace.HostNamespace;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.namespace.NativeNamespace;
import org.osgi.framework.namespace.PackageNamespace;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.resource.Namespace;

/**
 * A factory for creating {@link ModuleRevisionBuilder}s based on OSGi bundle manifests.
 * @since 3.10
 * @noinstantiate This class is not intended to be instantiated by clients.
 */
public final class OSGiManifestBuilderFactory {
	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 ATTR_OLD_REPRIVIDE = "reprovide"; //$NON-NLS-1$
	private static final String HEADER_OLD_PROVIDE_PACKAGE = "Provide-Package"; //$NON-NLS-1$
	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};
	private static final Collection SYSTEM_CAPABILITIES = Collections.unmodifiableCollection(Arrays.asList(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE, NativeNamespace.NATIVE_NAMESPACE));
	private static final Collection PROHIBITED_CAPABILITIES = Collections.unmodifiableCollection(Arrays.asList(IdentityNamespace.IDENTITY_NAMESPACE));

	/**
	 * Creates a builder for the specified bundle manifest
	 * @param manifest the bundle manifest
	 * @return a builder for the specified bundle manifest
	 * @throws BundleException if the bundle manifest is invalid
	 */
	public static ModuleRevisionBuilder createBuilder(Map manifest) throws BundleException {
		return createBuilder(manifest, null, null, null);
	}

	/**
	 * Creates a builder for the specified bundle manifest.  An alias can be supplied
	 * for the symbolic name.  Also extra package exports and extra provided capabilities
	 * may be specified outside of the supplied manifest.  This is useful for creating
	 * a builder for the system module which takes into account the configuration
	 * properties {@link Constants#FRAMEWORK_SYSTEMPACKAGES_EXTRA} and
	 * {@link Constants#FRAMEWORK_SYSTEMCAPABILITIES_EXTRA}.
	 * @param manifest the bundle manifest
	 * @param symbolicNameAlias the symbolic name alias.  A null value is allowed.
	 * @param extraExports the extra package exports.  A null value is allowed.
	 * @param extraCapabilities the extra provided capabilities.   A null value is allowed.
	 * @return a builder for the specified bundle manifest
	 * @throws BundleException if the bundle manifest is invalid
	 */
	public static ModuleRevisionBuilder createBuilder(Map manifest, String symbolicNameAlias, String extraExports, String extraCapabilities) throws BundleException {
		ModuleRevisionBuilder builder = new ModuleRevisionBuilder();

		int manifestVersion = getManifestVersion(manifest);
		if (manifestVersion >= 2) {
			validateHeaders(manifest, extraExports != null);
		}

		Object symbolicName = getSymbolicNameAndVersion(builder, manifest, symbolicNameAlias, manifestVersion);

		Collection> exportedPackages = new ArrayList<>();
		getPackageExports(builder, ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, manifest.get(Constants.EXPORT_PACKAGE)), symbolicName, exportedPackages);
		getPackageExports(builder, ManifestElement.parseHeader(HEADER_OLD_PROVIDE_PACKAGE, manifest.get(HEADER_OLD_PROVIDE_PACKAGE)), symbolicName, exportedPackages);
		if (extraExports != null && !extraExports.isEmpty()) {
			getPackageExports(builder, ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, extraExports), symbolicName, exportedPackages);
		}
		getPackageImports(builder, manifest, exportedPackages, manifestVersion);

		getRequireBundle(builder, ManifestElement.parseHeader(Constants.REQUIRE_BUNDLE, manifest.get(Constants.REQUIRE_BUNDLE)));

		getProvideCapabilities(builder, ManifestElement.parseHeader(Constants.PROVIDE_CAPABILITY, manifest.get(Constants.PROVIDE_CAPABILITY)), extraCapabilities == null);
		if (extraCapabilities != null && !extraCapabilities.isEmpty()) {
			getProvideCapabilities(builder, ManifestElement.parseHeader(Constants.PROVIDE_CAPABILITY, extraCapabilities), false);
		}
		getRequireCapabilities(builder, ManifestElement.parseHeader(Constants.REQUIRE_CAPABILITY, manifest.get(Constants.REQUIRE_CAPABILITY)));

		addRequireEclipsePlatform(builder, manifest);

		getEquinoxDataCapability(builder, manifest);

		getFragmentHost(builder, ManifestElement.parseHeader(Constants.FRAGMENT_HOST, manifest.get(Constants.FRAGMENT_HOST)));

		convertBREEs(builder, manifest);

		getNativeCode(builder, manifest);
		return builder;
	}

	private static void validateHeaders(Map manifest, boolean allowJavaExports) throws BundleException {
		for (String definedOSGiValidateHeader : DEFINED_OSGI_VALIDATE_HEADERS) {
			String header = manifest.get(definedOSGiValidateHeader);
			if (header != null) {
				ManifestElement[] elements = ManifestElement.parseHeader(definedOSGiValidateHeader, header);
				checkForDuplicateDirectivesAttributes(definedOSGiValidateHeader, elements);
				if (definedOSGiValidateHeader == Constants.IMPORT_PACKAGE) {
					checkImportExportSyntax(definedOSGiValidateHeader, elements, false, false, false);
				}
				if (definedOSGiValidateHeader == Constants.DYNAMICIMPORT_PACKAGE) {
					checkImportExportSyntax(definedOSGiValidateHeader, elements, false, true, false);
				}
				if (definedOSGiValidateHeader == Constants.EXPORT_PACKAGE) {
					checkImportExportSyntax(definedOSGiValidateHeader, elements, true, false, allowJavaExports);
				}
				if (definedOSGiValidateHeader == Constants.FRAGMENT_HOST) {
					checkExtensionBundle(definedOSGiValidateHeader, elements, manifest);
				}
			} else if (definedOSGiValidateHeader == Constants.BUNDLE_SYMBOLICNAME) {
				throw new BundleException(Constants.BUNDLE_SYMBOLICNAME + " header is required.", BundleException.MANIFEST_ERROR); //$NON-NLS-1$
			}
		}
	}

	@SuppressWarnings("deprecation")
	private static void checkImportExportSyntax(String headerKey, ManifestElement[] elements, boolean export,
			boolean dynamic, boolean allowJavaExports) throws BundleException {
		if (elements == null)
			return;
		int length = elements.length;
		Set packages = new HashSet<>(length);
		for (int i = 0; i < length; i++) {
			// check for duplicate imports
			String[] packageNames = elements[i].getValueComponents();
			for (String packageName : packageNames) {
				if (!export && !dynamic && packages.contains(packageName)) {
					String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, headerKey, elements[i].toString());
					throw new BundleException(message + " : " + NLS.bind(Msg.HEADER_PACKAGE_DUPLICATES, packageName), BundleException.MANIFEST_ERROR); //$NON-NLS-1$
				}
				// check for java.*
				if (export && !allowJavaExports && packageName.startsWith("java.")) { //$NON-NLS-1$
					String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, headerKey, elements[i].toString());
					throw new BundleException(message + " : " + NLS.bind(Msg.HEADER_PACKAGE_JAVA, packageName), BundleException.MANIFEST_ERROR); //$NON-NLS-1$
				}
				packages.add(packageName);
			}
			// 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(Msg.HEADER_VERSION_ERROR, Constants.VERSION_ATTRIBUTE, Constants.PACKAGE_SPECIFICATION_VERSION), BundleException.MANIFEST_ERROR);
			}
			// check for bundle-symbolic-name and bundle-version attributes on exports
			// (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(Msg.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 + " : " + Msg.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 (ManifestElement element : elements) {
			Enumeration directiveKeys = element.getDirectiveKeys();
			if (directiveKeys != null) {
				while (directiveKeys.hasMoreElements()) {
					String key = directiveKeys.nextElement();
					String[] directives = element.getDirectives(key);
					if (directives.length > 1) {
						String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, headerKey, element.toString());
						throw new BundleException(NLS.bind(message + " : " + Msg.HEADER_DIRECTIVE_DUPLICATES, key), BundleException.MANIFEST_ERROR); //$NON-NLS-1$
					}
				}
			}
			Enumeration attrKeys = element.getKeys();
			if (attrKeys != null) {
				while (attrKeys.hasMoreElements()) {
					String key = attrKeys.nextElement();
					String[] attrs = element.getAttributes(key);
					if (attrs.length > 1) {
						String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, headerKey, element.toString());
						throw new BundleException(message + " : " + NLS.bind(Msg.HEADER_ATTRIBUTE_DUPLICATES, key), BundleException.MANIFEST_ERROR); //$NON-NLS-1$
					}
				}
			}
		}
	}

	private static void checkExtensionBundle(String headerKey, ManifestElement[] elements, Map manifest) throws BundleException {
		if (elements.length == 0)
			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)) {
			if (elements[0].getDirective(Constants.EXTENSION_DIRECTIVE) != null) {
				String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, headerKey, elements[0].toString());
				throw new BundleException(message + " : " + NLS.bind(Msg.HEADER_EXTENSION_ERROR, hostName), BundleException.MANIFEST_ERROR); //$NON-NLS-1$
			}
		} else {
			if (manifest.get(Constants.REQUIRE_BUNDLE) != null)
				throw new BundleException(Msg.OSGiManifestBuilderFactory_ExtensionReqBundleError, BundleException.MANIFEST_ERROR);
			if (manifest.get(Constants.BUNDLE_NATIVECODE) != null)
				throw new BundleException(Msg.OSGiManifestBuilderFactory_ExtensionNativeError, BundleException.MANIFEST_ERROR);

		}
	}

	private static int getManifestVersion(Map manifest) {
		String manifestVersionHeader = manifest.get(Constants.BUNDLE_MANIFESTVERSION);
		return manifestVersionHeader == null ? 1 : Integer.parseInt(manifestVersionHeader);
	}

	private static Object getSymbolicNameAndVersion(ModuleRevisionBuilder builder, Map manifest, String symbolicNameAlias, int manifestVersion) throws BundleException {
		boolean isFragment = manifest.get(Constants.FRAGMENT_HOST) != null;
		builder.setTypes(isFragment ? BundleRevision.TYPE_FRAGMENT : 0);
		String version = manifest.get(Constants.BUNDLE_VERSION);
		try {
			builder.setVersion((version != null) ? Version.parseVersion(version) : Version.emptyVersion);
		} catch (IllegalArgumentException ex) {
			if (manifestVersion >= 2) {
				String message = NLS.bind(Msg.OSGiManifestBuilderFactory_InvalidManifestError, Constants.BUNDLE_VERSION, version);
				throw new BundleException(message, BundleException.MANIFEST_ERROR, ex);
			}
			// prior to R4 the Bundle-Version header was not interpreted by the Framework;
			// must not fail for old R3 style bundles
		}

		Object symbolicName = null;
		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];
				builder.setSymbolicName(bsnElement.getValue());
				if (symbolicNameAlias != null) {
					List result = new ArrayList<>();
					result.add(builder.getSymbolicName());
					result.add(symbolicNameAlias);
					symbolicName = result;
				} else {
					symbolicName = builder.getSymbolicName();
				}
				Map directives = getDirectives(bsnElement);
				directives.remove(BundleNamespace.CAPABILITY_USES_DIRECTIVE);
				directives.remove(BundleNamespace.CAPABILITY_EFFECTIVE_DIRECTIVE);
				Map attributes = getAttributes(bsnElement);
				if (!directives.containsKey(IdentityNamespace.CAPABILITY_SINGLETON_DIRECTIVE)) {
					// previous versions of equinox treated the singleton attribute as a directive
					Object singletonAttr = attributes.get(IdentityNamespace.CAPABILITY_SINGLETON_DIRECTIVE);
					if ("true".equals(singletonAttr)) { //$NON-NLS-1$
						directives.put(IdentityNamespace.CAPABILITY_SINGLETON_DIRECTIVE, (String) singletonAttr);
					}
				}
				if (!isFragment) {
					// create the bundle namespace
					Map bundleAttributes = new HashMap<>(attributes);
					bundleAttributes.put(BundleNamespace.BUNDLE_NAMESPACE, symbolicName);
					bundleAttributes.put(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, builder.getVersion());
					builder.addCapability(BundleNamespace.BUNDLE_NAMESPACE, directives, bundleAttributes);

					// create the host namespace
					// only if the directive is not never
					if (!HostNamespace.FRAGMENT_ATTACHMENT_NEVER.equals(directives.get(HostNamespace.CAPABILITY_FRAGMENT_ATTACHMENT_DIRECTIVE))) {
						Map hostAttributes = new HashMap<>(attributes);
						hostAttributes.put(HostNamespace.HOST_NAMESPACE, symbolicName);
						hostAttributes.put(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, builder.getVersion());
						builder.addCapability(HostNamespace.HOST_NAMESPACE, directives, hostAttributes);
					}
				}
				// every bundle that has a symbolic name gets an identity;
				// never use the symbolic name alias for the identity namespace
				Map identityAttributes = new HashMap<>(attributes);
				identityAttributes.put(IdentityNamespace.IDENTITY_NAMESPACE, builder.getSymbolicName());
				identityAttributes.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, builder.getVersion());
				identityAttributes.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, isFragment ? IdentityNamespace.TYPE_FRAGMENT : IdentityNamespace.TYPE_BUNDLE);
				builder.addCapability(IdentityNamespace.IDENTITY_NAMESPACE, directives, identityAttributes);
			}
		}

		return symbolicName == null ? symbolicNameAlias : symbolicName;
	}

	private static void getPackageExports(ModuleRevisionBuilder builder, ManifestElement[] exportElements, Object symbolicName, Collection> exportedPackages) throws BundleException {
		if (exportElements == null)
			return;
		for (ManifestElement exportElement : exportElements) {
			String[] packageNames = exportElement.getValueComponents();
			Map attributes = getAttributes(exportElement);
			Map directives = getDirectives(exportElement);
			directives.remove(PackageNamespace.CAPABILITY_EFFECTIVE_DIRECTIVE);
			String versionAttr = (String) attributes.remove(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
			@SuppressWarnings("deprecation")
			String specVersionAttr = (String) attributes.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
			Version version = versionAttr == null ? parsePackageVersion(packageNames, specVersionAttr)
					: parsePackageVersion(packageNames, versionAttr);
			attributes.put(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, version);
			if (symbolicName != null) {
				attributes.put(PackageNamespace.CAPABILITY_BUNDLE_SYMBOLICNAME_ATTRIBUTE, symbolicName);
			}
			attributes.put(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, builder.getVersion());
			for (String packageName : packageNames) {
				Map packageAttrs = new HashMap<>(attributes);
				packageAttrs.put(PackageNamespace.PACKAGE_NAMESPACE, packageName);
				builder.addCapability(PackageNamespace.PACKAGE_NAMESPACE, directives, packageAttrs);
				exportedPackages.add(packageAttrs);
			}
		}
	}

	private static Version parsePackageVersion(String[] packageNames, String versionSpec) throws BundleException {
		try {
			return versionSpec == null ? Version.emptyVersion : Version.parseVersion(versionSpec);
		} catch (IllegalArgumentException e) {
			throw new BundleException(
					"Invalid value for version specified for package(s) " + Arrays.toString(packageNames), //$NON-NLS-1$
					BundleException.MANIFEST_ERROR, e);
		}
	}

	private static void getPackageImports(ModuleRevisionBuilder builder, Map manifest, Collection> exportedPackages, int manifestVersion) throws BundleException {
		Collection importPackageNames = new ArrayList<>();
		ManifestElement[] importElements = ManifestElement.parseHeader(Constants.IMPORT_PACKAGE, manifest.get(Constants.IMPORT_PACKAGE));
		ManifestElement[] dynamicImportElements = ManifestElement.parseHeader(Constants.DYNAMICIMPORT_PACKAGE, manifest.get(Constants.DYNAMICIMPORT_PACKAGE));
		addPackageImports(builder, importElements, importPackageNames, false);
		addPackageImports(builder, dynamicImportElements, importPackageNames, true);
		if (manifestVersion < 2)
			addImplicitImports(builder, exportedPackages, importPackageNames);
	}

	private static void addPackageImports(ModuleRevisionBuilder builder, ManifestElement[] importElements, Collection importPackageNames, boolean dynamic) throws BundleException {
		if (importElements == null)
			return;
		for (ManifestElement importElement : importElements) {
			String[] packageNames = importElement.getValueComponents();
			Map attributes = getAttributes(importElement);
			Map directives = getDirectives(importElement);
			directives.remove(PackageNamespace.REQUIREMENT_EFFECTIVE_DIRECTIVE);
			directives.remove(PackageNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE);
			if (dynamic) {
				directives.put(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE, PackageNamespace.RESOLUTION_DYNAMIC);
			}
			String versionRangeAttr = (String) attributes.remove(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
			@SuppressWarnings("deprecation")
			String specVersionRangeAttr = (String) attributes.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
			VersionRange versionRange = versionRangeAttr == null ? (specVersionRangeAttr == null ? null : new VersionRange(specVersionRangeAttr)) : new VersionRange(versionRangeAttr);
			String bundleVersionRangeAttr = (String) attributes.remove(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
			VersionRange bundleVersionRange = bundleVersionRangeAttr == null ? null : new VersionRange(bundleVersionRangeAttr);
			// the attribute "optional" used to be used in old versions of equinox to specify optional imports
			// preserving behavior for compatibility
			Object optionalAttr = attributes.remove(Namespace.RESOLUTION_OPTIONAL);
			for (String packageName : packageNames) {
				if (!dynamic) {
					importPackageNames.add(packageName);
				}
				// fill in the filter directive based on the attributes
				Map packageDirectives = new HashMap<>(directives);
				StringBuilder filter = new StringBuilder();
				filter.append('(').append(PackageNamespace.PACKAGE_NAMESPACE).append('=').append(packageName).append(')');
				int size = filter.length();
				for (Map.Entry attribute : attributes.entrySet())
					filter.append('(').append(attribute.getKey()).append('=').append(attribute.getValue()).append(')');
				if (versionRange != null)
					filter.append(versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE));
				if (bundleVersionRange != null)
					filter.append(bundleVersionRange.toFilterString(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE));
				if (size != filter.length())
					// need to add (&...)
					filter.insert(0, "(&").append(')'); //$NON-NLS-1$
				packageDirectives.put(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString());

				// fill in cardinality for dynamic wild cards
				if (dynamic && packageName.indexOf('*') >= 0)
					packageDirectives.put(PackageNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE, PackageNamespace.CARDINALITY_MULTIPLE);

				// check the old optional attribute
				if ("true".equals(optionalAttr) && packageDirectives.get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE) == null) { //$NON-NLS-1$
					packageDirectives.put(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE, Namespace.RESOLUTION_OPTIONAL);
				}
				builder.addRequirement(PackageNamespace.PACKAGE_NAMESPACE, packageDirectives, new HashMap<>(0));
			}
		}
	}

	private static void addImplicitImports(ModuleRevisionBuilder builder, Collection> exportedPackages, Collection importPackageNames) {
		for (Map exportAttributes : exportedPackages) {
			String packageName = (String) exportAttributes.get(PackageNamespace.PACKAGE_NAMESPACE);
			if (importPackageNames.contains(packageName))
				continue;
			importPackageNames.add(packageName);
			Version packageVersion = (Version) exportAttributes.get(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
			StringBuilder filter = new StringBuilder();
			filter.append("(&(").append(PackageNamespace.PACKAGE_NAMESPACE).append('=').append(packageName).append(')'); //$NON-NLS-1$
			filter.append('(').append(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE).append(">=").append(packageVersion).append("))"); //$NON-NLS-1$//$NON-NLS-2$
			Map directives = new HashMap<>(1);
			directives.put(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString());
			builder.addRequirement(PackageNamespace.PACKAGE_NAMESPACE, directives, new HashMap<>(0));
		}
	}

	private static Map getDirectives(ManifestElement element) {
		Map directives = new HashMap<>();
		Enumeration keys = element.getDirectiveKeys();
		if (keys == null)
			return directives;
		while (keys.hasMoreElements()) {
			String key = keys.nextElement();
			directives.put(key, element.getDirective(key));
		}
		return directives;
	}

	private static void getRequireBundle(ModuleRevisionBuilder builder, ManifestElement[] requireBundles) throws BundleException {
		if (requireBundles == null)
			return;
		for (ManifestElement requireElement : requireBundles) {
			String[] bundleNames = requireElement.getValueComponents();
			Map attributes = getAttributes(requireElement);
			Map directives = getDirectives(requireElement);
			directives.remove(BundleNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE);
			directives.remove(BundleNamespace.REQUIREMENT_EFFECTIVE_DIRECTIVE);
			String versionRangeAttr = (String) attributes.remove(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
			VersionRange versionRange = versionRangeAttr == null ? null : new VersionRange(versionRangeAttr);
			// These two attrs are used as directives in previous versions of equinox
			// Preserving behavior for compatibility reasons.
			Object optionalAttr = attributes.remove(Namespace.RESOLUTION_OPTIONAL);
			Object reprovideAttr = attributes.remove(ATTR_OLD_REPRIVIDE);
			for (String bundleName : bundleNames) {
				if (bundleName.equals(builder.getSymbolicName())) {
					// ignore requirements to ourself
					continue;
				}
				// fill in the filter directive based on the attributes
				Map bundleDirectives = new HashMap<>(directives);
				StringBuilder filter = new StringBuilder();
				filter.append('(').append(BundleNamespace.BUNDLE_NAMESPACE).append('=').append(bundleName).append(')');
				int size = filter.length();
				for (Map.Entry attribute : attributes.entrySet())
					filter.append('(').append(attribute.getKey()).append('=').append(attribute.getValue()).append(')');
				if (versionRange != null)
					filter.append(versionRange.toFilterString(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE));
				if (size != filter.length())
					// need to add (&...)
					filter.insert(0, "(&").append(')'); //$NON-NLS-1$
				bundleDirectives.put(BundleNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString());
				// check the old compatibility attributes
				if ("true".equals(optionalAttr) && bundleDirectives.get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE) == null) { //$NON-NLS-1$
					bundleDirectives.put(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE, Namespace.RESOLUTION_OPTIONAL);
				}
				if ("true".equals(reprovideAttr) && bundleDirectives.get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE) == null) { //$NON-NLS-1$
					bundleDirectives.put(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE, BundleNamespace.VISIBILITY_REEXPORT);
				}
				builder.addRequirement(BundleNamespace.BUNDLE_NAMESPACE, bundleDirectives, new HashMap<>(0));
			}
		}
	}

	private static void getFragmentHost(ModuleRevisionBuilder builder, ManifestElement[] fragmentHosts) throws BundleException {
		if (fragmentHosts == null || fragmentHosts.length == 0)
			return;

		ManifestElement fragmentHost = fragmentHosts[0];
		String hostName = fragmentHost.getValue();
		Map attributes = getAttributes(fragmentHost);
		Map directives = getDirectives(fragmentHost);
		directives.remove(HostNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE);
		directives.remove(HostNamespace.REQUIREMENT_EFFECTIVE_DIRECTIVE);

		String versionRangeAttr = (String) attributes.remove(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
		VersionRange versionRange = versionRangeAttr == null ? null : new VersionRange(versionRangeAttr);

		// fill in the filter directive based on the attributes
		StringBuilder filter = new StringBuilder();
		filter.append('(').append(HostNamespace.HOST_NAMESPACE).append('=').append(hostName).append(')');
		int size = filter.length();
		for (Map.Entry attribute : attributes.entrySet())
			filter.append('(').append(attribute.getKey()).append('=').append(attribute.getValue()).append(')');
		if (versionRange != null)
			filter.append(versionRange.toFilterString(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE));
		if (size != filter.length())
			// need to add (&...)
			filter.insert(0, "(&").append(')'); //$NON-NLS-1$
		directives.put(BundleNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString());
		builder.addRequirement(HostNamespace.HOST_NAMESPACE, directives, new HashMap<>(0));
		// Add a fragment capability to advertise what host this resource is providing a fragment for
		directives = Collections.singletonMap(EquinoxModuleDataNamespace.CAPABILITY_EFFECTIVE_DIRECTIVE, EquinoxModuleDataNamespace.EFFECTIVE_INFORMATION);
		builder.addCapability(EquinoxFragmentNamespace.FRAGMENT_NAMESPACE, directives, Collections.singletonMap(EquinoxFragmentNamespace.FRAGMENT_NAMESPACE, hostName));
	}

	private static void getProvideCapabilities(ModuleRevisionBuilder builder, ManifestElement[] provideElements, boolean checkSystemCapabilities) throws BundleException {
		if (provideElements == null)
			return;
		for (ManifestElement provideElement : provideElements) {
			String[] namespaces = provideElement.getValueComponents();
			Map attributes = getAttributes(provideElement);
			Map directives = getDirectives(provideElement);
			for (String namespace : namespaces) {
				if (PROHIBITED_CAPABILITIES.contains(namespace) || (checkSystemCapabilities && SYSTEM_CAPABILITIES.contains(namespace))) {
					throw new BundleException("A bundle is not allowed to define a capability in the " + namespace + " name space.", BundleException.MANIFEST_ERROR); //$NON-NLS-1$ //$NON-NLS-2$
				}

				builder.addCapability(namespace, directives, attributes);
			}
		}
	}

	private static void getRequireCapabilities(ModuleRevisionBuilder builder, ManifestElement[] requireElements) throws BundleException {
		if (requireElements == null)
			return;
		for (ManifestElement requireElement : requireElements) {
			String[] namespaces = requireElement.getValueComponents();
			Map attributes = getAttributes(requireElement);
			Map directives = getDirectives(requireElement);
			for (String namespace : namespaces) {
				builder.addRequirement(namespace, directives, attributes);
			}
		}
	}

	private static void addRequireEclipsePlatform(ModuleRevisionBuilder builder, Map manifest) {
		String platformFilter = manifest.get(EclipsePlatformNamespace.ECLIPSE_PLATFORM_FILTER_HEADER);
		if (platformFilter == null) {
			return;
		}
		// only support one
		HashMap directives = new HashMap<>();
		directives.put(EclipsePlatformNamespace.REQUIREMENT_FILTER_DIRECTIVE, platformFilter);
		builder.addRequirement(EclipsePlatformNamespace.ECLIPSE_PLATFORM_NAMESPACE, directives, Collections.emptyMap());
	}

	@SuppressWarnings("deprecation")
	private static void getEquinoxDataCapability(ModuleRevisionBuilder builder, Map manifest) throws BundleException {
		Map attributes = new HashMap<>();

		// Get the activation policy attributes
		ManifestElement[] policyElements = ManifestElement.parseHeader(Constants.BUNDLE_ACTIVATIONPOLICY, manifest.get(Constants.BUNDLE_ACTIVATIONPOLICY));
		if (policyElements != null) {
			ManifestElement policy = policyElements[0];
			String policyName = policy.getValue();
			if (EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY_LAZY.equals(policyName)) {
				attributes.put(EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY, policyName);
				String includeSpec = policy.getDirective(Constants.INCLUDE_DIRECTIVE);
				if (includeSpec != null) {
					attributes.put(EquinoxModuleDataNamespace.CAPABILITY_LAZY_INCLUDE_ATTRIBUTE, convertValueWithNoWhitespace("List", includeSpec)); //$NON-NLS-1$
				}
				String excludeSpec = policy.getDirective(Constants.EXCLUDE_DIRECTIVE);
				if (excludeSpec != null) {
					attributes.put(EquinoxModuleDataNamespace.CAPABILITY_LAZY_EXCLUDE_ATTRIBUTE, convertValueWithNoWhitespace("List", excludeSpec)); //$NON-NLS-1$
				}
			}
		} else {
			policyElements = ManifestElement.parseHeader(EquinoxModuleDataNamespace.LAZYSTART_HEADER, manifest.get(EquinoxModuleDataNamespace.LAZYSTART_HEADER));
			if (policyElements == null) {
				policyElements = ManifestElement.parseHeader(EquinoxModuleDataNamespace.AUTOSTART_HEADER, manifest.get(EquinoxModuleDataNamespace.AUTOSTART_HEADER));
			}
			if (policyElements != null) {
				ManifestElement policy = policyElements[0];
				String excludeSpec = policy.getAttribute(EquinoxModuleDataNamespace.LAZYSTART_EXCEPTIONS_ATTRIBUTE);
				if ("true".equals(policy.getValue())) { //$NON-NLS-1$
					attributes.put(EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY, EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY_LAZY);
					if (excludeSpec != null) {
						attributes.put(EquinoxModuleDataNamespace.CAPABILITY_LAZY_EXCLUDE_ATTRIBUTE, convertValueWithNoWhitespace("List", excludeSpec)); //$NON-NLS-1$
					}
				} else {
					// NOTICE - the exclude list gets converted to an include list when the header is not true
					if (excludeSpec != null) {
						attributes.put(EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY, EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY_LAZY);
						attributes.put(EquinoxModuleDataNamespace.CAPABILITY_LAZY_INCLUDE_ATTRIBUTE, convertValueWithNoWhitespace("List", excludeSpec)); //$NON-NLS-1$
					}
				}
			}
		}

		// Get the activator
		String activator = manifest.get(Constants.BUNDLE_ACTIVATOR);
		if (activator == null && manifest.get(Constants.FRAGMENT_HOST) != null) {
			// we look for the extension activator for fragments
			// probably should do this only for framework extensions, but there is no harm to check for others
			// it is only acted upon for framework extension fragments
			activator = manifest.get(Constants.EXTENSION_BUNDLE_ACTIVATOR);
		}
		if (activator != null) {
			attributes.put(EquinoxModuleDataNamespace.CAPABILITY_ACTIVATOR, activator);
		}

		// Get the class path
		ManifestElement[] classpathElements = ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, manifest.get(Constants.BUNDLE_CLASSPATH));
		if (classpathElements != null) {
			List classpath = new ArrayList<>();
			for (ManifestElement element : classpathElements) {
				String[] components = element.getValueComponents();
				Collections.addAll(classpath, components);
			}
			attributes.put(EquinoxModuleDataNamespace.CAPABILITY_CLASSPATH, classpath);
		}

		// Get the buddy policy list
		ManifestElement[] buddyPolicies = ManifestElement.parseHeader(EquinoxModuleDataNamespace.BUDDY_POLICY_HEADER, manifest.get(EquinoxModuleDataNamespace.BUDDY_POLICY_HEADER));
		if (buddyPolicies != null) {
			List policies = new ArrayList<>();
			for (ManifestElement element : buddyPolicies) {
				Collections.addAll(policies, element.getValueComponents());
			}
			attributes.put(EquinoxModuleDataNamespace.CAPABILITY_BUDDY_POLICY, policies);
		}

		// Get the registered buddy list
		ManifestElement[] registeredBuddies = ManifestElement.parseHeader(EquinoxModuleDataNamespace.REGISTERED_BUDDY_HEADER, manifest.get(EquinoxModuleDataNamespace.REGISTERED_BUDDY_HEADER));
		if (registeredBuddies != null) {
			List buddies = new ArrayList<>();
			for (ManifestElement element : registeredBuddies) {
				Collections.addAll(buddies, element.getValueComponents());
			}
			attributes.put(EquinoxModuleDataNamespace.CAPABILITY_BUDDY_REGISTERED, buddies);
		}

		// only create the capability if the attributes is not empty
		if (!attributes.isEmpty()) {
			Map directives = Collections.singletonMap(EquinoxModuleDataNamespace.CAPABILITY_EFFECTIVE_DIRECTIVE, EquinoxModuleDataNamespace.EFFECTIVE_INFORMATION);
			builder.addCapability(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE, directives, attributes);
		}
	}

	private static Map getAttributes(ManifestElement element) throws BundleException {
		Enumeration keys = element.getKeys();
		Map attributes = new HashMap<>();
		if (keys == null)
			return attributes;
		while (keys.hasMoreElements()) {
			String key = keys.nextElement();
			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();
			}
			attributes.put(key, convertValue(type, value));
		}
		return attributes;
	}

	private static Object convertValueWithNoWhitespace(String type, String value) throws BundleException {
		value = value.replaceAll("\\s", ""); //$NON-NLS-1$//$NON-NLS-2$
		return convertValue(type, value);
	}

	private static Object convertValue(String type, String value) throws BundleException {
		if (ATTR_TYPE_STRING.equalsIgnoreCase(type)) {
			return value;
		}

		String trimmed = value.trim();
		if (ATTR_TYPE_DOUBLE.equalsIgnoreCase(type)) {
			return Double.valueOf(trimmed);
		} else if (ATTR_TYPE_LONG.equalsIgnoreCase(type)) {
			return Long.valueOf(trimmed);
		} else if (ATTR_TYPE_URI.equalsIgnoreCase(type)) {
			// we no longer actually create URIs here; just use the string
			return trimmed;
		} else if (ATTR_TYPE_VERSION.equalsIgnoreCase(type)) {
			return new Version(trimmed);
		} else if (ATTR_TYPE_SET.equalsIgnoreCase(type)) {
			// just use List here so we don't have to deal with String[] in other places
			return Collections.unmodifiableList(Arrays.asList(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 BundleException("Unsupported type: " + type, BundleException.MANIFEST_ERROR); //$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 BundleException("Invalid type, missing ending '>' : " + type, BundleException.MANIFEST_ERROR); //$NON-NLS-1$
		}
		List tokens = new Tokenizer(value).getEscapedTokens(","); //$NON-NLS-1$
		List components = new ArrayList<>();
		for (String component : tokens) {
			components.add(convertValue(componentType, component));
		}
		return Collections.unmodifiableList(components);
	}

	private static void convertBREEs(ModuleRevisionBuilder builder, Map manifest) throws BundleException {
		@SuppressWarnings("deprecation")
		String[] brees = ManifestElement.getArrayFromList(manifest.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT));
		if (brees == null || brees.length == 0)
			return;
		List breeFilters = new ArrayList<>();
		for (String bree : brees)
			breeFilters.add(createOSGiEERequirementFilter(bree));
		String filterSpec;
		if (breeFilters.size() == 1) {
			filterSpec = breeFilters.get(0);
		} else {
			StringBuilder filterBuf = new StringBuilder("(|"); //$NON-NLS-1$
			for (String breeFilter : breeFilters) {
				filterBuf.append(breeFilter);
			}
			filterSpec = filterBuf.append(")").toString(); //$NON-NLS-1$
		}

		Map directives = new HashMap<>(1);
		directives.put(ExecutionEnvironmentNamespace.REQUIREMENT_FILTER_DIRECTIVE, filterSpec);
		builder.addRequirement(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE, directives, new HashMap<>(0));
	}

	static String escapeFilterInput(final String value) {
		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 '*' :
				case '\\' :
				case '(' :
				case ')' :
					output[cursor] = '\\';
					cursor++;
					escaped = true;
					break;
			}

			output[cursor] = c;
			cursor++;
		}

		return escaped ? new String(output, 0, cursor) : value;
	}

	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.", BundleException.MANIFEST_ERROR, e1); //$NON-NLS-1$
			}
		}
		return filterSpec;
	}

	private 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[] {escapeFilterInput(eeName), v1};
	}

	static class NativeClause implements Comparable {
		private final int manifestIndex;
		final List nativePaths;
		final String filter;
		private final Version highestFloor;
		private final boolean hasLanguage;

		NativeClause(int manifestIndex, ManifestElement clause) throws BundleException {
			this.manifestIndex = manifestIndex;
			this.nativePaths = new ArrayList<>(Arrays.asList(clause.getValueComponents()));
			StringBuilder sb = new StringBuilder();
			sb.append("(&"); //$NON-NLS-1$
			addToNativeCodeFilter(sb, clause, Constants.BUNDLE_NATIVECODE_OSNAME);
			addToNativeCodeFilter(sb, clause, Constants.BUNDLE_NATIVECODE_PROCESSOR);
			this.highestFloor = (Version) addToNativeCodeFilter(sb, clause, Constants.BUNDLE_NATIVECODE_OSVERSION);
			this.hasLanguage = ((Boolean) addToNativeCodeFilter(sb, clause, Constants.BUNDLE_NATIVECODE_LANGUAGE)).booleanValue();
			String selectionFilter = clause.getAttribute(Constants.SELECTION_FILTER_ATTRIBUTE);
			if (selectionFilter != null) {
				// do a sanity check to make sure the filter is valid
				try {
					FrameworkUtil.createFilter(selectionFilter);
				} catch (InvalidSyntaxException e) {
					throw new BundleException("Bad native code selection-filter.", BundleException.MANIFEST_ERROR, e); //$NON-NLS-1$
				}
				sb.append(selectionFilter);
			}
			sb.append(')');
			String filterResult = sb.toString();
			if (filterResult.equals("(&)")) { //$NON-NLS-1$
				// no matching attributes found just match all osnames
				filterResult = "(" + NativeNamespace.CAPABILITY_OSNAME_ATTRIBUTE + "=*)"; //$NON-NLS-1$ //$NON-NLS-2$
			}
			this.filter = filterResult;
		}

		private static Object addToNativeCodeFilter(StringBuilder filter, ManifestElement nativeCode, String attribute) {
			Boolean hasLanguage = Boolean.FALSE;
			Version highestFloor = null;
			String[] attrValues = nativeCode.getAttributes(attribute);
			if (attrValues != null) {

				String filterAttribute = attribute;
				if (Constants.BUNDLE_NATIVECODE_OSNAME.equals(attribute)) {
					filterAttribute = NativeNamespace.CAPABILITY_OSNAME_ATTRIBUTE;
				} else if (Constants.BUNDLE_NATIVECODE_PROCESSOR.equals(attribute)) {
					filterAttribute = NativeNamespace.CAPABILITY_PROCESSOR_ATTRIBUTE;
				} else if (Constants.BUNDLE_NATIVECODE_LANGUAGE.equals(attribute)) {
					filterAttribute = NativeNamespace.CAPABILITY_LANGUAGE_ATTRIBUTE;
					hasLanguage = attrValues.length > 0;
				} else if (Constants.BUNDLE_NATIVECODE_OSVERSION.equals(attribute)) {
					filterAttribute = NativeNamespace.CAPABILITY_OSVERSION_ATTRIBUTE;
				}

				if (attrValues.length > 1) {
					filter.append("(|"); //$NON-NLS-1$
				}
				for (String attrAlias : attrValues) {
					if (NativeNamespace.CAPABILITY_OSVERSION_ATTRIBUTE.equals(filterAttribute)) {
						VersionRange range = new VersionRange(attrAlias);
						if (highestFloor == null || highestFloor.compareTo(range.getLeft()) < 0) {
							highestFloor = range.getLeft();
						}
						filter.append(range.toFilterString(filterAttribute));
					} else {
						filter.append('(').append(filterAttribute).append("~=").append(escapeFilterInput(attrAlias)).append(')'); //$NON-NLS-1$
					}
				}
				if (attrValues.length > 1) {
					filter.append(')');
				}
			}
			return Constants.BUNDLE_NATIVECODE_LANGUAGE.equals(attribute) ? hasLanguage : highestFloor;
		}

		@Override
		public int compareTo(NativeClause other) {
			if (this.highestFloor != null) {
				if (other == null) {
					return -1;
				}
				if (other.highestFloor == null) {
					return -1;
				}
				int compareVersions = this.highestFloor.compareTo(other.highestFloor);
				if (compareVersions != 0) {
					return -(compareVersions);
				}
			} else if (other.highestFloor != null) {
				return 1;
			}
			if (this.hasLanguage) {
				return other.hasLanguage ? this.manifestIndex - other.manifestIndex : 1;
			}
			return other.hasLanguage ? -1 : this.manifestIndex - other.manifestIndex;
		}
	}

	private static void getNativeCode(ModuleRevisionBuilder builder, Map manifest) throws BundleException {
		ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_NATIVECODE, manifest.get(Constants.BUNDLE_NATIVECODE));
		if (elements == null) {
			return;
		}

		boolean optional = false;

		List nativeClauses = new ArrayList<>();
		for (int i = 0; i < elements.length; i++) {
			if (i == elements.length - 1) {
				optional = elements[i].getValue().equals("*"); //$NON-NLS-1$
			}
			if (!optional) {
				nativeClauses.add(new NativeClause(i, elements[i]));
			}
		}
		Collections.sort(nativeClauses);

		int numNativePaths = nativeClauses.size();
		if (numNativePaths == 0) {
			String msg = "No native code clauses found in the value of " + Constants.BUNDLE_NATIVECODE + ": " + manifest.get(Constants.BUNDLE_NATIVECODE); //$NON-NLS-1$//$NON-NLS-2$
			throw new BundleException(msg, BundleException.MANIFEST_ERROR);
		}
		StringBuilder allNativeFilters = new StringBuilder();
		if (numNativePaths > 1) {
			allNativeFilters.append("(|"); //$NON-NLS-1$
		}
		Map attributes = new HashMap<>(2);
		for (int i = 0; i < numNativePaths; i++) {
			NativeClause nativeClause = nativeClauses.get(i);
			if (numNativePaths == 1) {
				attributes.put(NativeCodeFinder.REQUIREMENT_NATIVE_PATHS_ATTRIBUTE, nativeClause.nativePaths);
			} else {
				attributes.put(NativeCodeFinder.REQUIREMENT_NATIVE_PATHS_ATTRIBUTE + '.' + i, nativeClause.nativePaths);
			}
			allNativeFilters.append(nativeClauses.get(i).filter);
		}
		if (numNativePaths > 1) {
			allNativeFilters.append(')');
		}

		Map directives = new HashMap<>(2);
		directives.put(NativeNamespace.REQUIREMENT_FILTER_DIRECTIVE, allNativeFilters.toString());
		if (optional) {
			directives.put(NativeNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE, NativeNamespace.RESOLUTION_OPTIONAL);
		}

		builder.addRequirement(NativeNamespace.NATIVE_NAMESPACE, directives, attributes);
	}
}