| /******************************************************************************* |
| * Copyright (c) 2012, 2018 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<String> SYSTEM_CAPABILITIES = Collections.unmodifiableCollection(Arrays.asList(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE, NativeNamespace.NATIVE_NAMESPACE)); |
| private static final Collection<String> 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<String, String> 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 <code>null</code> value is allowed. |
| * @param extraExports the extra package exports. A <code>null</code> value is allowed. |
| * @param extraCapabilities the extra provided capabilities. A <code>null</code> value is allowed. |
| * @return a builder for the specified bundle manifest |
| * @throws BundleException if the bundle manifest is invalid |
| */ |
| public static ModuleRevisionBuilder createBuilder(Map<String, String> manifest, String symbolicNameAlias, String extraExports, String extraCapabilities) throws BundleException { |
| ModuleRevisionBuilder builder = new ModuleRevisionBuilder(); |
| |
| int manifestVersion = getManifestVersion(manifest); |
| if (manifestVersion >= 2) { |
| validateHeaders(manifest); |
| } |
| |
| Object symbolicName = getSymbolicNameAndVersion(builder, manifest, symbolicNameAlias, manifestVersion); |
| |
| Collection<Map<String, Object>> 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<String, String> manifest) 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); |
| } |
| if (definedOSGiValidateHeader == Constants.DYNAMICIMPORT_PACKAGE) { |
| checkImportExportSyntax(definedOSGiValidateHeader, elements, false, true); |
| } |
| if (definedOSGiValidateHeader == Constants.EXPORT_PACKAGE) { |
| checkImportExportSyntax(definedOSGiValidateHeader, elements, true, false); |
| } |
| 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) 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(Msg.HEADER_PACKAGE_DUPLICATES, packageName), BundleException.MANIFEST_ERROR); //$NON-NLS-1$ |
| } |
| // check for java.* |
| if (export && 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-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(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<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 + " : " + Msg.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(Msg.HEADER_ATTRIBUTE_DUPLICATES, key), BundleException.MANIFEST_ERROR); //$NON-NLS-1$ |
| } |
| } |
| } |
| } |
| } |
| |
| private static void checkExtensionBundle(String headerKey, ManifestElement[] elements, Map<String, String> 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<String, String> manifest) { |
| String manifestVersionHeader = manifest.get(Constants.BUNDLE_MANIFESTVERSION); |
| return manifestVersionHeader == null ? 1 : Integer.parseInt(manifestVersionHeader); |
| } |
| |
| private static Object getSymbolicNameAndVersion(ModuleRevisionBuilder builder, Map<String, String> 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<String> result = new ArrayList<>(); |
| result.add(builder.getSymbolicName()); |
| result.add(symbolicNameAlias); |
| symbolicName = result; |
| } else { |
| symbolicName = builder.getSymbolicName(); |
| } |
| Map<String, String> directives = getDirectives(bsnElement); |
| directives.remove(BundleNamespace.CAPABILITY_USES_DIRECTIVE); |
| directives.remove(BundleNamespace.CAPABILITY_EFFECTIVE_DIRECTIVE); |
| Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<Map<String, Object>> exportedPackages) throws BundleException { |
| if (exportElements == null) |
| return; |
| for (ManifestElement exportElement : exportElements) { |
| String[] packageNames = exportElement.getValueComponents(); |
| Map<String, Object> attributes = getAttributes(exportElement); |
| Map<String, String> 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 ? (specVersionAttr == null ? Version.emptyVersion : Version.parseVersion(specVersionAttr)) : Version.parseVersion(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<String, Object> packageAttrs = new HashMap<>(attributes); |
| packageAttrs.put(PackageNamespace.PACKAGE_NAMESPACE, packageName); |
| builder.addCapability(PackageNamespace.PACKAGE_NAMESPACE, directives, packageAttrs); |
| exportedPackages.add(packageAttrs); |
| } |
| } |
| } |
| |
| private static void getPackageImports(ModuleRevisionBuilder builder, Map<String, String> manifest, Collection<Map<String, Object>> exportedPackages, int manifestVersion) throws BundleException { |
| Collection<String> 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<String> importPackageNames, boolean dynamic) throws BundleException { |
| if (importElements == null) |
| return; |
| for (ManifestElement importElement : importElements) { |
| String[] packageNames = importElement.getValueComponents(); |
| Map<String, Object> attributes = getAttributes(importElement); |
| Map<String, String> 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<String, String> 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<String, Object> 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<String, Object>(0)); |
| } |
| } |
| } |
| |
| private static void addImplicitImports(ModuleRevisionBuilder builder, Collection<Map<String, Object>> exportedPackages, Collection<String> importPackageNames) { |
| for (Map<String, Object> 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<String, String> directives = new HashMap<>(1); |
| directives.put(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString()); |
| builder.addRequirement(PackageNamespace.PACKAGE_NAMESPACE, directives, new HashMap<String, Object>(0)); |
| } |
| } |
| |
| private static Map<String, String> getDirectives(ManifestElement element) { |
| Map<String, String> directives = new HashMap<>(); |
| Enumeration<String> 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<String, Object> attributes = getAttributes(requireElement); |
| Map<String, String> 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<String, String> 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<String, Object> 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<String, Object>(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<String, Object> attributes = getAttributes(fragmentHost); |
| Map<String, String> 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<String, Object> 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<String, Object>(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.<String, Object> 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<String, Object> attributes = getAttributes(provideElement); |
| Map<String, String> 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<String, Object> attributes = getAttributes(requireElement); |
| Map<String, String> directives = getDirectives(requireElement); |
| for (String namespace : namespaces) { |
| builder.addRequirement(namespace, directives, attributes); |
| } |
| } |
| } |
| |
| private static void addRequireEclipsePlatform(ModuleRevisionBuilder builder, Map<String, String> manifest) { |
| String platformFilter = manifest.get(EclipsePlatformNamespace.ECLIPSE_PLATFORM_FILTER_HEADER); |
| if (platformFilter == null) { |
| return; |
| } |
| // only support one |
| HashMap<String, String> directives = new HashMap<>(); |
| directives.put(EclipsePlatformNamespace.REQUIREMENT_FILTER_DIRECTIVE, platformFilter); |
| builder.addRequirement(EclipsePlatformNamespace.ECLIPSE_PLATFORM_NAMESPACE, directives, Collections.<String, Object> emptyMap()); |
| } |
| |
| @SuppressWarnings("deprecation") |
| private static void getEquinoxDataCapability(ModuleRevisionBuilder builder, Map<String, String> manifest) throws BundleException { |
| Map<String, Object> 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<String>", includeSpec)); //$NON-NLS-1$ |
| } |
| String excludeSpec = policy.getDirective(Constants.EXCLUDE_DIRECTIVE); |
| if (excludeSpec != null) { |
| attributes.put(EquinoxModuleDataNamespace.CAPABILITY_LAZY_EXCLUDE_ATTRIBUTE, convertValueWithNoWhitespace("List<String>", 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<String>", 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<String>", 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<String> 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<String> 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<String> 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<String, String> directives = Collections.singletonMap(EquinoxModuleDataNamespace.CAPABILITY_EFFECTIVE_DIRECTIVE, EquinoxModuleDataNamespace.EFFECTIVE_INFORMATION); |
| builder.addCapability(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE, directives, attributes); |
| } |
| } |
| |
| private static Map<String, Object> getAttributes(ManifestElement element) throws BundleException { |
| Enumeration<String> keys = element.getKeys(); |
| Map<String, Object> 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 new Double(trimmed); |
| } else if (ATTR_TYPE_LONG.equalsIgnoreCase(type)) { |
| return new Long(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<String> 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<String> tokens = new Tokenizer(value).getEscapedTokens(","); //$NON-NLS-1$ |
| List<Object> components = new ArrayList<>(); |
| for (String component : tokens) { |
| components.add(convertValue(componentType, component)); |
| } |
| return Collections.unmodifiableList(components); |
| } |
| |
| private static void convertBREEs(ModuleRevisionBuilder builder, Map<String, String> manifest) throws BundleException { |
| @SuppressWarnings("deprecation") |
| String[] brees = ManifestElement.getArrayFromList(manifest.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT)); |
| if (brees == null || brees.length == 0) |
| return; |
| 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$ |
| } |
| |
| Map<String, String> directives = new HashMap<>(1); |
| directives.put(ExecutionEnvironmentNamespace.REQUIREMENT_FILTER_DIRECTIVE, filterSpec); |
| builder.addRequirement(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE, directives, new HashMap<String, Object>(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<NativeClause> { |
| private final int manifestIndex; |
| final List<String> 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<String, String> manifest) throws BundleException { |
| ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_NATIVECODE, manifest.get(Constants.BUNDLE_NATIVECODE)); |
| if (elements == null) { |
| return; |
| } |
| |
| boolean optional = false; |
| |
| List<NativeClause> 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<String, Object> 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<String, String> 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); |
| } |
| } |