/*******************************************************************************
 * Copyright (c) 2003, 2020 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
 *     Danail Nachev -  ProSyst - bug 218625
 *******************************************************************************/
package org.eclipse.osgi.internal.resolver;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.service.resolver.BundleDescription;
import org.eclipse.osgi.service.resolver.BundleSpecification;
import org.eclipse.osgi.service.resolver.ExportPackageDescription;
import org.eclipse.osgi.service.resolver.GenericDescription;
import org.eclipse.osgi.service.resolver.GenericSpecification;
import org.eclipse.osgi.service.resolver.HostSpecification;
import org.eclipse.osgi.service.resolver.ImportPackageSpecification;
import org.eclipse.osgi.service.resolver.NativeCodeSpecification;
import org.eclipse.osgi.service.resolver.State;
import org.eclipse.osgi.service.resolver.VersionRange;
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.InvalidSyntaxException;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.BundleNamespace;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.resource.Namespace;

/**
 * This class builds bundle description objects from manifests
 */
public class StateBuilder {
	private static final String[] DEFINED_EXPORT_PACKAGE_DIRECTIVES = {Constants.USES_DIRECTIVE, Constants.INCLUDE_DIRECTIVE, Constants.EXCLUDE_DIRECTIVE, StateImpl.FRIENDS_DIRECTIVE, StateImpl.INTERNAL_DIRECTIVE, Constants.MANDATORY_DIRECTIVE};
	private static final String[] DEFINED_IMPORT_PACKAGE_DIRECTIVES = {Constants.RESOLUTION_DIRECTIVE};
	private static final String[] DEFINED_PACKAGE_MATCHING_ATTRS = {Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, Constants.BUNDLE_VERSION_ATTRIBUTE, Constants.PACKAGE_SPECIFICATION_VERSION, Constants.VERSION_ATTRIBUTE};
	private static final String[] DEFINED_REQUIRE_BUNDLE_DIRECTIVES = {Constants.RESOLUTION_DIRECTIVE, Constants.VISIBILITY_DIRECTIVE};
	private static final String[] DEFINED_FRAGMENT_HOST_DIRECTIVES = {Constants.EXTENSION_DIRECTIVE};
	static final String[] DEFINED_BSN_DIRECTIVES = {Constants.SINGLETON_DIRECTIVE, Constants.FRAGMENT_ATTACHMENT_DIRECTIVE, Constants.MANDATORY_DIRECTIVE};
	static final String[] DEFINED_BSN_MATCHING_ATTRS = {Constants.BUNDLE_VERSION_ATTRIBUTE, StateImpl.OPTIONAL_ATTRIBUTE, StateImpl.REPROVIDE_ATTRIBUTE};
	private static final String[] DEFINED_REQUIRE_CAPABILITY_DIRECTIVES = {Constants.RESOLUTION_DIRECTIVE, Constants.FILTER_DIRECTIVE, Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE};
	private static final String[] DEFINED_REQUIRE_CAPABILITY_ATTRS = {};
	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};
	static final String GENERIC_REQUIRE = "Eclipse-GenericRequire"; //$NON-NLS-1$
	static final String GENERIC_CAPABILITY = "Eclipse-GenericCapability"; //$NON-NLS-1$

	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 OPTIONAL_ATTR = "optional"; //$NON-NLS-1$
	private static final String MULTIPLE_ATTR = "multiple"; //$NON-NLS-1$
	private static final String TRUE = "true"; //$NON-NLS-1$

	static BundleDescription createBundleDescription(StateImpl state, Dictionary<String, String> manifest, String location) throws BundleException {
		BundleDescriptionImpl result = new BundleDescriptionImpl();
		String manifestVersionHeader = manifest.get(Constants.BUNDLE_MANIFESTVERSION);
		boolean jreBundle = "true".equals(manifest.get(StateImpl.Eclipse_JREBUNDLE)); //$NON-NLS-1$
		int manifestVersion = 1;
		if (manifestVersionHeader != null)
			manifestVersion = Integer.parseInt(manifestVersionHeader);
		if (manifestVersion >= 2)
			validateHeaders(manifest, jreBundle);

		// retrieve the symbolic-name and the singleton status
		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];
				result.setSymbolicName(bsnElement.getValue());
				String singleton = bsnElement.getDirective(Constants.SINGLETON_DIRECTIVE);
				if (singleton == null) // TODO this is for backward compatibility; need to check manifest version < 2 to allow this after everyone has converted to new syntax
					singleton = bsnElement.getAttribute(Constants.SINGLETON_DIRECTIVE);
				result.setStateBit(BundleDescriptionImpl.SINGLETON, "true".equals(singleton)); //$NON-NLS-1$
				String fragmentAttachment = bsnElement.getDirective(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE);
				if (fragmentAttachment != null) {
					if (fragmentAttachment.equals(Constants.FRAGMENT_ATTACHMENT_RESOLVETIME)) {
						result.setStateBit(BundleDescriptionImpl.ATTACH_FRAGMENTS, true);
						result.setStateBit(BundleDescriptionImpl.DYNAMIC_FRAGMENTS, false);
					} else if (fragmentAttachment.equals(Constants.FRAGMENT_ATTACHMENT_NEVER)) {
						result.setStateBit(BundleDescriptionImpl.ATTACH_FRAGMENTS, false);
						result.setStateBit(BundleDescriptionImpl.DYNAMIC_FRAGMENTS, false);
					}
				}
				result.setDirective(Constants.MANDATORY_DIRECTIVE, ManifestElement.getArrayFromList(bsnElement.getDirective(Constants.MANDATORY_DIRECTIVE)));
				result.setAttributes(getAttributes(bsnElement, DEFINED_BSN_MATCHING_ATTRS));
				result.setArbitraryDirectives(getDirectives(bsnElement, DEFINED_BSN_DIRECTIVES));
			}
		}
		// retrieve other headers
		String version = manifest.get(Constants.BUNDLE_VERSION);
		try {
			result.setVersion((version != null) ? Version.parseVersion(version) : Version.emptyVersion);
		} catch (IllegalArgumentException ex) {
			if (manifestVersion >= 2) {
				String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, Constants.BUNDLE_VERSION, version);
				throw new BundleException(message + " : " + ex.getMessage(), BundleException.MANIFEST_ERROR, ex); //$NON-NLS-1$
			}
			// prior to R4 the Bundle-Version header was not interpreted by the Framework;
			// must not fail for old R3 style bundles
		}
		result.setLocation(location);
		result.setPlatformFilter(manifest.get(StateImpl.ECLIPSE_PLATFORMFILTER));
		String[] brees = ManifestElement.getArrayFromList(manifest.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT));
		result.setExecutionEnvironments(brees);
		ManifestElement[] host = ManifestElement.parseHeader(Constants.FRAGMENT_HOST, manifest.get(Constants.FRAGMENT_HOST));
		if (host != null)
			result.setHost(createHostSpecification(host[0], state));
		ManifestElement[] exports = ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, manifest.get(Constants.EXPORT_PACKAGE));
		ManifestElement[] provides = ManifestElement.parseHeader(StateImpl.PROVIDE_PACKAGE, manifest.get(StateImpl.PROVIDE_PACKAGE));
		boolean strict = state != null && state.inStrictMode();
		List<String> providedExports = new ArrayList<>(provides == null ? 0 : provides.length);
		result.setExportPackages(createExportPackages(exports, provides, providedExports, strict));
		ManifestElement[] imports = ManifestElement.parseHeader(Constants.IMPORT_PACKAGE, manifest.get(Constants.IMPORT_PACKAGE));
		ManifestElement[] dynamicImports = ManifestElement.parseHeader(Constants.DYNAMICIMPORT_PACKAGE, manifest.get(Constants.DYNAMICIMPORT_PACKAGE));
		result.setImportPackages(createImportPackages(result.getExportPackages(), providedExports, imports, dynamicImports, manifestVersion));
		ManifestElement[] requires = ManifestElement.parseHeader(Constants.REQUIRE_BUNDLE, manifest.get(Constants.REQUIRE_BUNDLE));
		result.setRequiredBundles(createRequiredBundles(requires));
		String[][] genericAliases = getGenericAliases(state);
		ManifestElement[] genericRequires = getGenericRequires(manifest, genericAliases);
		ManifestElement[] osgiRequires = ManifestElement.parseHeader(Constants.REQUIRE_CAPABILITY, manifest.get(Constants.REQUIRE_CAPABILITY));
		result.setGenericRequires(createGenericRequires(genericRequires, osgiRequires, brees));
		ManifestElement[] genericCapabilities = getGenericCapabilities(manifest, genericAliases);
		ManifestElement[] osgiCapabilities = ManifestElement.parseHeader(Constants.PROVIDE_CAPABILITY, manifest.get(Constants.PROVIDE_CAPABILITY));
		result.setGenericCapabilities(createGenericCapabilities(genericCapabilities, osgiCapabilities, result));
		ManifestElement[] nativeCode = ManifestElement.parseHeader(Constants.BUNDLE_NATIVECODE, manifest.get(Constants.BUNDLE_NATIVECODE));
		result.setNativeCodeSpecification(createNativeCode(nativeCode));
		return result;
	}

	private static ManifestElement[] getGenericRequires(Dictionary<String, String> manifest, String[][] genericAliases) throws BundleException {
		ManifestElement[] genericRequires = ManifestElement.parseHeader(GENERIC_REQUIRE, manifest.get(GENERIC_REQUIRE));
		List<ManifestElement> aliasList = null;
		if (genericAliases.length > 0) {
			aliasList = new ArrayList<>(genericRequires == null ? 0 : genericRequires.length);
			for (String[] genericAlias : genericAliases) {
				ManifestElement[] aliasReqs = ManifestElement.parseHeader(genericAlias[1], manifest.get(genericAlias[1]));
				if (aliasReqs == null)
					continue;
				for (ManifestElement aliasReq : aliasReqs) {
					StringBuilder strBuf = new StringBuilder();
					strBuf.append(aliasReq.getValue()).append(':').append(genericAlias[2]);
					String filter = aliasReq.getAttribute(Constants.SELECTION_FILTER_ATTRIBUTE);
					if (filter != null)
						strBuf.append("; ").append(Constants.SELECTION_FILTER_ATTRIBUTE).append(filter).append("=\"").append(filter).append("\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					ManifestElement[] withType = ManifestElement.parseHeader(genericAlias[1], strBuf.toString());
					aliasList.add(withType[0]);
				}
			}
		}
		if (aliasList == null || aliasList.size() == 0)
			return genericRequires;
		if (genericRequires != null)
			Collections.addAll(aliasList, genericRequires);
		return aliasList.toArray(new ManifestElement[aliasList.size()]);
	}

	private static ManifestElement[] getGenericCapabilities(Dictionary<String, String> manifest, String[][] genericAliases) throws BundleException {
		ManifestElement[] genericCapabilities = ManifestElement.parseHeader(GENERIC_CAPABILITY, manifest.get(GENERIC_CAPABILITY));
		List<ManifestElement> aliasList = null;
		if (genericAliases.length > 0) {
			aliasList = new ArrayList<>(genericCapabilities == null ? 0 : genericCapabilities.length);
			for (String[] genericAlias : genericAliases) {
				ManifestElement[] aliasCapabilities = ManifestElement.parseHeader(genericAlias[0], manifest.get(genericAlias[0]));
				if (aliasCapabilities == null)
					continue;
				for (ManifestElement aliasCapability : aliasCapabilities) {
					StringBuilder strBuf = new StringBuilder();
					strBuf.append(aliasCapability.getValue()).append(':').append(genericAlias[2]);
					for (Enumeration<String> keys = aliasCapability.getKeys(); keys != null && keys.hasMoreElements();) {
						String key = keys.nextElement();
						strBuf.append("; ").append(key).append("=\"").append(aliasCapability.getAttribute(key)).append("\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}
					ManifestElement[] withTypes = ManifestElement.parseHeader(genericAlias[0], strBuf.toString());
					aliasList.add(withTypes[0]);
				}
			}
		}
		if (aliasList == null || aliasList.size() == 0)
			return genericCapabilities;
		if (genericCapabilities != null)
			Collections.addAll(aliasList, genericCapabilities);
		return aliasList.toArray(new ManifestElement[aliasList.size()]);
	}

	private static String[][] getGenericAliases(StateImpl state) {
		String genericAliasesProp = getPlatformProperty(state, "osgi.genericAliases"); //$NON-NLS-1$
		if (genericAliasesProp == null)
			return new String[0][0];
		String[] aliases = ManifestElement.getArrayFromList(genericAliasesProp, ","); //$NON-NLS-1$
		String[][] result = new String[aliases.length][];
		for (int i = 0; i < aliases.length; i++)
			result[i] = ManifestElement.getArrayFromList(aliases[i], ":"); //$NON-NLS-1$
		return result;
	}

	private static String getPlatformProperty(State state, String key) {
		Dictionary<Object, Object>[] platformProps = state == null ? null : state.getPlatformProperties();
		return platformProps == null || platformProps.length == 0 ? null : (String) platformProps[0].get(key);
	}

	private static void validateHeaders(Dictionary<String, String> manifest, boolean jreBundle) 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, jreBundle);
				}
				if (definedOSGiValidateHeader == Constants.DYNAMICIMPORT_PACKAGE) {
					checkImportExportSyntax(definedOSGiValidateHeader, elements, false, true, jreBundle);
				}
				if (definedOSGiValidateHeader == Constants.EXPORT_PACKAGE) {
					checkImportExportSyntax(definedOSGiValidateHeader, elements, true, false, jreBundle);
				}
				if (definedOSGiValidateHeader == Constants.FRAGMENT_HOST) {
					checkExtensionBundle(definedOSGiValidateHeader, elements);
				}
			} else if (definedOSGiValidateHeader == Constants.BUNDLE_SYMBOLICNAME) {
				throw new BundleException(NLS.bind(StateMsg.HEADER_REQUIRED, Constants.BUNDLE_SYMBOLICNAME), BundleException.MANIFEST_ERROR);
			}
		}
	}

	private static BundleSpecification[] createRequiredBundles(ManifestElement[] specs) {
		if (specs == null)
			return null;
		BundleSpecification[] result = new BundleSpecification[specs.length];
		for (int i = 0; i < specs.length; i++)
			result[i] = createRequiredBundle(specs[i]);
		return result;
	}

	static BundleSpecification createRequiredBundle(ManifestElement spec) {
		BundleSpecificationImpl result = new BundleSpecificationImpl();
		result.setName(spec.getValue());
		result.setVersionRange(getVersionRange(spec.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE)));
		result.setExported(Constants.VISIBILITY_REEXPORT.equals(spec.getDirective(Constants.VISIBILITY_DIRECTIVE)) || "true".equals(spec.getAttribute(StateImpl.REPROVIDE_ATTRIBUTE))); //$NON-NLS-1$
		result.setOptional(Constants.RESOLUTION_OPTIONAL.equals(spec.getDirective(Constants.RESOLUTION_DIRECTIVE)) || "true".equals(spec.getAttribute(StateImpl.OPTIONAL_ATTRIBUTE))); //$NON-NLS-1$
		result.setAttributes(getAttributes(spec, DEFINED_BSN_MATCHING_ATTRS));
		result.setArbitraryDirectives(getDirectives(spec, DEFINED_REQUIRE_BUNDLE_DIRECTIVES));
		return result;
	}

	private static ImportPackageSpecification[] createImportPackages(ExportPackageDescription[] exported, List<String> providedExports, ManifestElement[] imported, ManifestElement[] dynamicImported, int manifestVersion) {
		List<ImportPackageSpecification> allImports = null;
		if (manifestVersion < 2) {
			// add implicit imports for each exported package if manifest verions is less than 2.
			if (exported.length == 0 && imported == null && dynamicImported == null)
				return null;
			allImports = new ArrayList<>(exported.length + (imported == null ? 0 : imported.length));
			for (ExportPackageDescription exportDescription : exported) {
				if (providedExports.contains(exportDescription.getName())) {
					continue;
				}
				ImportPackageSpecificationImpl result = new ImportPackageSpecificationImpl();
				result.setName(exportDescription.getName());
				result.setVersionRange(getVersionRange(exportDescription.getVersion().toString()));
				result.setDirective(Constants.RESOLUTION_DIRECTIVE, ImportPackageSpecification.RESOLUTION_STATIC);
				allImports.add(result);
			}
		} else {
			allImports = new ArrayList<>(imported == null ? 0 : imported.length);
		}

		// add dynamics first so they will get overriden by static imports if
		// the same package is dyanamically imported and statically imported.
		if (dynamicImported != null)
			for (ManifestElement dynamicImport : dynamicImported) {
				addImportPackages(dynamicImport, allImports, manifestVersion, true);
			}
		if (imported != null)
			for (ManifestElement pkgImport : imported) {
				addImportPackages(pkgImport, allImports, manifestVersion, false);
			}
		return allImports.toArray(new ImportPackageSpecification[allImports.size()]);
	}

	public static void addImportPackages(ManifestElement importPackage, List<ImportPackageSpecification> allImports, int manifestVersion, boolean dynamic) {
		String[] importNames = importPackage.getValueComponents();
		for (String importName : importNames) {
			// do not allow for multiple imports of same package of manifest version < 2
			if (manifestVersion < 2) {
				Iterator<ImportPackageSpecification> iter = allImports.iterator();
				while (iter.hasNext()) {
					if (importName.equals(iter.next().getName())) {
						iter.remove();
					}
				}
			}
			ImportPackageSpecificationImpl result = new ImportPackageSpecificationImpl();
			result.setName(importName);
			// set common attributes for both dynamic and static imports
			String versionString = importPackage.getAttribute(Constants.VERSION_ATTRIBUTE);
			if (versionString == null) // specification-version aliases to version
				versionString = importPackage.getAttribute(Constants.PACKAGE_SPECIFICATION_VERSION);
			result.setVersionRange(getVersionRange(versionString));
			result.setBundleSymbolicName(importPackage.getAttribute(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE));
			result.setBundleVersionRange(getVersionRange(importPackage.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE)));
			// only set the matching attributes if manifest version >= 2
			if (manifestVersion >= 2)
				result.setAttributes(getAttributes(importPackage, DEFINED_PACKAGE_MATCHING_ATTRS));
			if (dynamic)
				result.setDirective(Constants.RESOLUTION_DIRECTIVE, ImportPackageSpecification.RESOLUTION_DYNAMIC);
			else
				result.setDirective(Constants.RESOLUTION_DIRECTIVE, getResolution(importPackage.getDirective(Constants.RESOLUTION_DIRECTIVE)));
			result.setArbitraryDirectives(getDirectives(importPackage, DEFINED_IMPORT_PACKAGE_DIRECTIVES));
			allImports.add(result);
		}
	}

	private static String getResolution(String resolution) {
		String result = ImportPackageSpecification.RESOLUTION_STATIC;
		if (Constants.RESOLUTION_OPTIONAL.equals(resolution) || ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(resolution))
			result = resolution;
		return result;
	}

	static ExportPackageDescription[] createExportPackages(ManifestElement[] exported, ManifestElement[] provides, List<String> providedExports, boolean strict) {
		int numExports = (exported == null ? 0 : exported.length) + (provides == null ? 0 : provides.length);
		if (numExports == 0)
			return null;
		List<ExportPackageDescription> allExports = new ArrayList<>(numExports);
		if (exported != null)
			for (ManifestElement packageExport : exported) {
				addExportPackages(packageExport, allExports, strict);
			}
		if (provides != null)
			addProvidePackages(provides, allExports, providedExports);
		return allExports.toArray(new ExportPackageDescription[allExports.size()]);
	}

	static void addExportPackages(ManifestElement exportPackage, List<ExportPackageDescription> allExports, boolean strict) {
		String[] exportNames = exportPackage.getValueComponents();
		for (String exportName : exportNames) {
			// if we are in strict mode and the package is marked as internal, skip it.
			if (strict && "true".equals(exportPackage.getDirective(StateImpl.INTERNAL_DIRECTIVE))) //$NON-NLS-1$
				continue;
			ExportPackageDescriptionImpl result = new ExportPackageDescriptionImpl();
			result.setName(exportName);
			String versionString = exportPackage.getAttribute(Constants.VERSION_ATTRIBUTE);
			if (versionString == null) // specification-version aliases to version
				versionString = exportPackage.getAttribute(Constants.PACKAGE_SPECIFICATION_VERSION);
			if (versionString != null)
				result.setVersion(Version.parseVersion(versionString));
			result.setDirective(Constants.USES_DIRECTIVE, ManifestElement.getArrayFromList(exportPackage.getDirective(Constants.USES_DIRECTIVE)));
			result.setDirective(Constants.INCLUDE_DIRECTIVE, exportPackage.getDirective(Constants.INCLUDE_DIRECTIVE));
			result.setDirective(Constants.EXCLUDE_DIRECTIVE, exportPackage.getDirective(Constants.EXCLUDE_DIRECTIVE));
			result.setDirective(StateImpl.FRIENDS_DIRECTIVE, ManifestElement.getArrayFromList(exportPackage.getDirective(StateImpl.FRIENDS_DIRECTIVE)));
			result.setDirective(StateImpl.INTERNAL_DIRECTIVE, Boolean.valueOf(exportPackage.getDirective(StateImpl.INTERNAL_DIRECTIVE)));
			result.setDirective(Constants.MANDATORY_DIRECTIVE, ManifestElement.getArrayFromList(exportPackage.getDirective(Constants.MANDATORY_DIRECTIVE)));
			result.setAttributes(getAttributes(exportPackage, DEFINED_PACKAGE_MATCHING_ATTRS));
			result.setArbitraryDirectives(getDirectives(exportPackage, DEFINED_EXPORT_PACKAGE_DIRECTIVES));
			allExports.add(result);
		}
	}

	private static void addProvidePackages(ManifestElement[] provides, List<ExportPackageDescription> allExports, List<String> providedExports) {
		ExportPackageDescription[] currentExports = allExports.toArray(new ExportPackageDescription[allExports.size()]);
		for (ManifestElement provide : provides) {
			boolean duplicate = false;
			for (ExportPackageDescription currentExport : currentExports) {
				if (provide.getValue().equals(currentExport.getName())) {
					duplicate = true;
					break;
				}
			}
			if (!duplicate) {
				ExportPackageDescriptionImpl result = new ExportPackageDescriptionImpl();
				result.setName(provide.getValue());
				allExports.add(result);
			}
			providedExports.add(provide.getValue());
		}
	}

	static Map<String, String> getDirectives(ManifestElement element, String[] definedDirectives) {
		Enumeration<String> keys = element.getDirectiveKeys();
		if (keys == null)
			return null;
		Map<String, String> arbitraryDirectives = null;
		keyloop: while (keys.hasMoreElements()) {
			String key = keys.nextElement();
			for (String definedDirective : definedDirectives) {
				if (definedDirective.equals(key))
					continue keyloop;
			}
			if (arbitraryDirectives == null)
				arbitraryDirectives = new HashMap<>();
			arbitraryDirectives.put(key, element.getDirective(key));
		}
		return arbitraryDirectives;
	}

	static Map<String, Object> getAttributes(ManifestElement element, String[] definedAttrs) {
		Enumeration<String> keys = element.getKeys();
		Map<String, Object> arbitraryAttrs = null;
		if (keys == null)
			return null;
		while (keys.hasMoreElements()) {
			boolean definedAttr = false;
			String key = keys.nextElement();
			for (String attr : definedAttrs) {
				if (attr.equals(key)) {
					definedAttr = true;
					break;
				}
			}
			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();
			}
			if (!definedAttr) {
				if (arbitraryAttrs == null)
					arbitraryAttrs = new HashMap<>();
				arbitraryAttrs.put(key, convertValue(type, value));
			}
		}
		return arbitraryAttrs;
	}

	private static Object convertValue(String type, String value) {

		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))
			try {
				Class<?> uriClazz = Class.forName("java.net.URI"); //$NON-NLS-1$
				Constructor<?> constructor = uriClazz.getConstructor(new Class[] {String.class});
				return constructor.newInstance(new Object[] {trimmed});
			} catch (ClassNotFoundException e) {
				// oh well cannot support; just use string
				return value;
			} catch (RuntimeException e) { // got some reflection exception
				throw e;
			} catch (Exception e) {
				throw new RuntimeException(e.getMessage(), e);
			}
		else if (ATTR_TYPE_VERSION.equalsIgnoreCase(type))
			return new Version(trimmed);
		else if (ATTR_TYPE_SET.equalsIgnoreCase(type))
			return 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 RuntimeException("Unsupported type: " + type); //$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 RuntimeException("Invalid type, missing ending '>' : " + type); //$NON-NLS-1$
		}
		List<String> tokens = new Tokenizer(value).getEscapedTokens(","); //$NON-NLS-1$
		List<Object> components = new ArrayList<>();
		for (String component : tokens) {
			components.add(convertValue(componentType, component));
		}
		return components;
	}

	static HostSpecification createHostSpecification(ManifestElement spec, State state) {
		if (spec == null)
			return null;
		HostSpecificationImpl result = new HostSpecificationImpl();
		result.setName(spec.getValue());
		result.setVersionRange(getVersionRange(spec.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE)));
		String multiple = spec.getDirective("multiple-hosts"); //$NON-NLS-1$
		if (multiple == null)
			multiple = getPlatformProperty(state, "osgi.support.multipleHosts"); //$NON-NLS-1$
		result.setIsMultiHost("true".equals(multiple)); //$NON-NLS-1$
		result.setAttributes(getAttributes(spec, DEFINED_BSN_MATCHING_ATTRS));
		result.setArbitraryDirectives(getDirectives(spec, DEFINED_FRAGMENT_HOST_DIRECTIVES));
		return result;
	}

	private static GenericSpecification[] createGenericRequires(ManifestElement[] equinoxRequires, ManifestElement[] osgiRequires, String[] brees) throws BundleException {
		List<GenericSpecification> result = createEquinoxRequires(equinoxRequires);
		result = createOSGiRequires(osgiRequires, result);
		result = convertBREEs(brees, result);
		return result == null ? null : result.toArray(new GenericSpecification[result.size()]);
	}

	static List<GenericSpecification> convertBREEs(String[] brees, List<GenericSpecification> result) throws BundleException {
		if (brees == null || brees.length == 0)
			return result;
		if (result == null)
			result = new ArrayList<>(brees.length);
		List<String> 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$
		}
		GenericSpecificationImpl spec = new GenericSpecificationImpl();
		spec.setResolution(GenericSpecificationImpl.RESOLUTION_FROM_BREE);
		spec.setType(StateImpl.OSGI_EE_NAMESPACE);
		try {
			FilterImpl filter = FilterImpl.newInstance(filterSpec);
			spec.setMatchingFilter(filter);
			String name = filter.getPrimaryKeyValue(spec.getType());
			if (name != null)
				spec.setName(name);
		} catch (InvalidSyntaxException e) {
			throw new BundleException("Error converting required execution environment.", e); //$NON-NLS-1$
		}
		result.add(spec);
		return result;
	}

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

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

	static List<GenericSpecification> createOSGiRequires(ManifestElement[] osgiRequires, List<GenericSpecification> result) throws BundleException {
		if (osgiRequires == null)
			return result;
		if (result == null)
			result = new ArrayList<>();
		for (ManifestElement element : osgiRequires) {
			String[] namespaces = element.getValueComponents();
			for (String namespace : namespaces) {
				GenericSpecificationImpl spec = new GenericSpecificationImpl();
				spec.setType(namespace);
				String filterSpec = element.getDirective(Constants.FILTER_DIRECTIVE);
				if (filterSpec != null) {
					try {
						FilterImpl filter = FilterImpl.newInstance(filterSpec);
						spec.setMatchingFilter(filter);
						String name = filter.getPrimaryKeyValue(namespace);
						if (name != null)
							spec.setName(name);
					} catch (InvalidSyntaxException e) {
						String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, Constants.REQUIRE_CAPABILITY, element.toString());
						throw new BundleException(message + " : filter", BundleException.MANIFEST_ERROR, e); //$NON-NLS-1$
					}
				}
				String resolutionDirective = element.getDirective(Constants.RESOLUTION_DIRECTIVE);
				int resolution = 0;
				if (Constants.RESOLUTION_OPTIONAL.equals(resolutionDirective))
					resolution |= GenericSpecification.RESOLUTION_OPTIONAL;
				String cardinality = element.getDirective(Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE);
				if (Namespace.CARDINALITY_MULTIPLE.equals(cardinality))
					resolution |= GenericSpecification.RESOLUTION_MULTIPLE;
				spec.setResolution(resolution);
				spec.setAttributes(getAttributes(element, DEFINED_REQUIRE_CAPABILITY_ATTRS));
				spec.setArbitraryDirectives(getDirectives(element, DEFINED_REQUIRE_CAPABILITY_DIRECTIVES));
				result.add(spec);
			}
		}
		return result;
	}

	private static List<GenericSpecification> createEquinoxRequires(ManifestElement[] equinoxRequires) throws BundleException {
		if (equinoxRequires == null)
			return null;
		ArrayList<GenericSpecification> results = new ArrayList<>(equinoxRequires.length);
		for (ManifestElement equinoxRequire : equinoxRequires) {
			String[] genericNames = equinoxRequire.getValueComponents();
			for (String genericName : genericNames) {
				GenericSpecificationImpl spec = new GenericSpecificationImpl();
				int colonIdx = genericName.indexOf(':');
				if (colonIdx > 0) {
					spec.setName(genericName.substring(0, colonIdx));
					spec.setType(genericName.substring(colonIdx + 1));
				} else {
					spec.setName(genericName);
				}
				try {
					spec.setMatchingFilter(equinoxRequire.getAttribute(Constants.SELECTION_FILTER_ATTRIBUTE), true);
				} catch (InvalidSyntaxException e) {
					String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, GENERIC_REQUIRE, equinoxRequire.toString());
					throw new BundleException(message + " : " + Constants.SELECTION_FILTER_ATTRIBUTE, BundleException.MANIFEST_ERROR, e); //$NON-NLS-1$
				}
				String optional = equinoxRequire.getAttribute(OPTIONAL_ATTR);
				String multiple = equinoxRequire.getAttribute(MULTIPLE_ATTR);
				int resolution = 0;
				if (TRUE.equals(optional))
					resolution |= GenericSpecification.RESOLUTION_OPTIONAL;
				if (TRUE.equals(multiple))
					resolution |= GenericSpecification.RESOLUTION_MULTIPLE;
				spec.setResolution(resolution);
				results.add(spec);
			}
		}
		return results;
	}

	private static GenericDescription[] createGenericCapabilities(ManifestElement[] equinoxCapabilities, ManifestElement[] osgiCapabilities, BundleDescription description) throws BundleException {
		List<GenericDescription> result = createEquinoxCapabilities(equinoxCapabilities);
		result = createOSGiCapabilities(osgiCapabilities, result, description);
		return result == null ? null : result.toArray(new GenericDescription[result.size()]);
	}

	static List<GenericDescription> createOSGiCapabilities(ManifestElement[] osgiCapabilities, List<GenericDescription> result, BundleDescription description) throws BundleException {
		if (result == null)
			result = new ArrayList<>(osgiCapabilities == null ? 1 : osgiCapabilities.length + 1);
		// Always have an osgi.identity capability if there is a symbolic name.
		GenericDescription osgiIdentity = createOsgiIdentityCapability(description);
		if (osgiIdentity != null)
			// always add the capability to the front
			result.add(0, osgiIdentity);
		return createOSGiCapabilities(osgiCapabilities, result, (Integer) null);
	}

	static List<GenericDescription> createOSGiCapabilities(ManifestElement[] osgiCapabilities, List<GenericDescription> result, Integer profileIndex) throws BundleException {
		if (osgiCapabilities == null)
			return result;
		if (result == null)
			result = new ArrayList<>(osgiCapabilities.length);

		for (ManifestElement element : osgiCapabilities) {
			String[] namespaces = element.getValueComponents();
			for (String namespace : namespaces) {
				if (IdentityNamespace.IDENTITY_NAMESPACE.equals(namespace))
					throw new BundleException("A bundle is not allowed to define a capability in the " + IdentityNamespace.IDENTITY_NAMESPACE + " name space."); //$NON-NLS-1$ //$NON-NLS-2$

				GenericDescriptionImpl desc = new GenericDescriptionImpl();
				desc.setType(namespace);
				Map<String, Object> mapAttrs = getAttributes(element, new String[0]);
				if (profileIndex != null)
					mapAttrs.put(ExportPackageDescriptionImpl.EQUINOX_EE, profileIndex);
				Dictionary<String, Object> attrs = mapAttrs == null ? new Hashtable<>() : new Hashtable<>(mapAttrs);
				desc.setAttributes(attrs);
				Map<String, String> directives = new HashMap<>();
				Enumeration<String> keys = element.getDirectiveKeys();
				if (keys != null)
					for (keys = element.getDirectiveKeys(); keys.hasMoreElements();) {
						String key = keys.nextElement();
						directives.put(key, element.getDirective(key));
					}
				desc.setDirectives(directives);
				result.add(desc);
			}
		}
		return result;
	}

	private static List<GenericDescription> createEquinoxCapabilities(ManifestElement[] equinoxCapabilities) throws BundleException {
		if (equinoxCapabilities == null)
			return null;
		ArrayList<GenericDescription> results = new ArrayList<>(equinoxCapabilities.length);
		for (ManifestElement equinoxCapability : equinoxCapabilities) {
			String[] genericNames = equinoxCapability.getValueComponents();
			for (String genericName : genericNames) {
				GenericDescriptionImpl desc = new GenericDescriptionImpl();
				String name = genericName;
				int colonIdx = genericName.indexOf(':');
				if (colonIdx > 0) {
					name = genericName.substring(0, colonIdx);
					desc.setType(genericName.substring(colonIdx + 1));
					if (IdentityNamespace.IDENTITY_NAMESPACE.equals(desc.getType()))
						throw new BundleException("A bundle is not allowed to define a capability in the " + IdentityNamespace.IDENTITY_NAMESPACE + " name space."); //$NON-NLS-1$ //$NON-NLS-2$
				}
				Map<String, Object> mapAttrs = getAttributes(equinoxCapability, new String[] {Constants.VERSION_ATTRIBUTE});
				Dictionary<String, Object> attrs = mapAttrs == null ? new Hashtable<>() : new Hashtable<>(mapAttrs);
				attrs.put(desc.getType(), name);
				String versionString = equinoxCapability.getAttribute(Constants.VERSION_ATTRIBUTE);
				if (versionString != null)
					attrs.put(Constants.VERSION_ATTRIBUTE, Version.parseVersion(versionString));
				desc.setAttributes(attrs);
				results.add(desc);
			}
		}
		return results;
	}

	private static NativeCodeSpecification createNativeCode(ManifestElement[] nativeCode) throws BundleException {
		if (nativeCode == null)
			return null;
		NativeCodeSpecificationImpl result = new NativeCodeSpecificationImpl();
		result.setName(Constants.BUNDLE_NATIVECODE);
		int length = nativeCode.length;
		if (length > 0 && nativeCode[length - 1].getValue().equals("*")) { //$NON-NLS-1$
			result.setOptional(true);
			length--;
		}
		NativeCodeDescriptionImpl[] suppliers = new NativeCodeDescriptionImpl[length];
		for (int i = 0; i < length; i++) {
			suppliers[i] = createNativeCodeDescription(nativeCode[i]);
		}
		result.setPossibleSuppliers(suppliers);
		return result;
	}

	private static NativeCodeDescriptionImpl createNativeCodeDescription(ManifestElement manifestElement) throws BundleException {
		NativeCodeDescriptionImpl result = new NativeCodeDescriptionImpl();
		result.setName(Constants.BUNDLE_NATIVECODE);
		result.setNativePaths(manifestElement.getValueComponents());
		result.setOSNames(manifestElement.getAttributes(Constants.BUNDLE_NATIVECODE_OSNAME));
		result.setProcessors(manifestElement.getAttributes(Constants.BUNDLE_NATIVECODE_PROCESSOR));
		result.setOSVersions(createVersionRanges(manifestElement.getAttributes(Constants.BUNDLE_NATIVECODE_OSVERSION)));
		result.setLanguages(manifestElement.getAttributes(Constants.BUNDLE_NATIVECODE_LANGUAGE));
		try {
			result.setFilter(manifestElement.getAttribute(Constants.SELECTION_FILTER_ATTRIBUTE));
		} catch (InvalidSyntaxException e) {
			String message = NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, Constants.BUNDLE_NATIVECODE, manifestElement.toString());
			throw new BundleException(message + " : " + Constants.SELECTION_FILTER_ATTRIBUTE, BundleException.MANIFEST_ERROR, e); //$NON-NLS-1$
		}
		return result;
	}

	private static VersionRange[] createVersionRanges(String[] ranges) {
		if (ranges == null)
			return null;
		VersionRange[] result = new VersionRange[ranges.length];
		for (int i = 0; i < result.length; i++)
			result[i] = new VersionRange(ranges[i]);
		return result;
	}

	private static VersionRange getVersionRange(String versionRange) {
		if (versionRange == null)
			return null;
		return new VersionRange(versionRange);
	}

	public static void checkImportExportSyntax(String headerKey, ManifestElement[] elements, boolean export, boolean dynamic, boolean jreBundle) throws BundleException {
		if (elements == null)
			return;
		int length = elements.length;
		Set<String> 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(StateMsg.HEADER_PACKAGE_DUPLICATES, packageName), BundleException.MANIFEST_ERROR); //$NON-NLS-1$
				}
				// check for java.*
				if (export && !jreBundle && 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(StateMsg.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(StateMsg.HEADER_VERSION_ERROR, Constants.VERSION_ATTRIBUTE, Constants.PACKAGE_SPECIFICATION_VERSION), BundleException.MANIFEST_ERROR);
			}
			// check for bundle-symbolic-name and bundle-verion attibures
			// (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(StateMsg.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 + " : " + StateMsg.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<String> 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 + " : " + StateMsg.HEADER_DIRECTIVE_DUPLICATES, key), BundleException.MANIFEST_ERROR); //$NON-NLS-1$
					}
				}
			}
			Enumeration<String> 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(StateMsg.HEADER_ATTRIBUTE_DUPLICATES, key), BundleException.MANIFEST_ERROR); //$NON-NLS-1$
					}
				}
			}
		}
	}

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

	static GenericDescription createOsgiIdentityCapability(BundleDescription description) {
		if (description.getSymbolicName() == null)
			return null;
		GenericDescriptionImpl result = new GenericDescriptionImpl();
		result.setType(IdentityNamespace.IDENTITY_NAMESPACE);
		Dictionary<String, Object> attributes = new Hashtable<>(description.getDeclaredAttributes());
		// remove osgi.wiring.bundle and bundle-version attributes
		attributes.remove(BundleNamespace.BUNDLE_NAMESPACE);
		attributes.remove(Constants.BUNDLE_VERSION_ATTRIBUTE);
		attributes.put(IdentityNamespace.IDENTITY_NAMESPACE, description.getSymbolicName());
		attributes.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, description.getHost() == null ? IdentityNamespace.TYPE_BUNDLE : IdentityNamespace.TYPE_FRAGMENT);
		attributes.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, description.getVersion());
		result.setAttributes(attributes);
		Map<String, String> directives = new HashMap<>(description.getDeclaredDirectives());
		// remove defaults directive values
		if (!description.isSingleton())
			directives.remove(Constants.SINGLETON_DIRECTIVE);
		if (description.attachFragments() && description.dynamicFragments())
			directives.remove(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE);
		result.setDirectives(directives);
		return result;
	}
}
