| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.felix.framework.util.manifestparser; |
| |
| import java.util.*; |
| import java.util.ArrayList; |
| import java.util.Map.Entry; |
| import org.apache.felix.framework.BundleRevisionImpl; |
| |
| import org.apache.felix.framework.Logger; |
| import org.apache.felix.framework.capabilityset.SimpleFilter; |
| import org.apache.felix.framework.wiring.BundleCapabilityImpl; |
| import org.apache.felix.framework.util.FelixConstants; |
| import org.apache.felix.framework.util.VersionRange; |
| import org.apache.felix.framework.wiring.BundleRequirementImpl; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Version; |
| import org.osgi.framework.wiring.BundleCapability; |
| import org.osgi.framework.wiring.BundleRequirement; |
| import org.osgi.framework.wiring.BundleRevision; |
| |
| public class ManifestParser |
| { |
| private final Logger m_logger; |
| private final Map m_configMap; |
| private final Map m_headerMap; |
| private volatile int m_activationPolicy = BundleRevisionImpl.EAGER_ACTIVATION; |
| private volatile String m_activationIncludeDir; |
| private volatile String m_activationExcludeDir; |
| private volatile boolean m_isExtension = false; |
| private volatile String m_bundleSymbolicName; |
| private volatile Version m_bundleVersion; |
| private volatile List<BundleCapability> m_capabilities; |
| private volatile List<BundleRequirement> m_requirements; |
| private volatile List<R4LibraryClause> m_libraryClauses; |
| private volatile boolean m_libraryHeadersOptional = false; |
| |
| public ManifestParser(Logger logger, Map configMap, BundleRevision owner, Map headerMap) |
| throws BundleException |
| { |
| m_logger = logger; |
| m_configMap = configMap; |
| m_headerMap = headerMap; |
| |
| // Verify that only manifest version 2 is specified. |
| String manifestVersion = getManifestVersion(m_headerMap); |
| if ((manifestVersion != null) && !manifestVersion.equals("2")) |
| { |
| throw new BundleException( |
| "Unknown 'Bundle-ManifestVersion' value: " + manifestVersion); |
| } |
| |
| // Create lists to hold capabilities and requirements. |
| List<BundleCapabilityImpl> capList = new ArrayList(); |
| |
| // |
| // Parse bundle version. |
| // |
| |
| m_bundleVersion = Version.emptyVersion; |
| if (headerMap.get(Constants.BUNDLE_VERSION) != null) |
| { |
| try |
| { |
| m_bundleVersion = Version.parseVersion( |
| (String) headerMap.get(Constants.BUNDLE_VERSION)); |
| } |
| catch (RuntimeException ex) |
| { |
| // R4 bundle versions must parse, R3 bundle version may not. |
| if (getManifestVersion().equals("2")) |
| { |
| throw ex; |
| } |
| m_bundleVersion = Version.emptyVersion; |
| } |
| } |
| |
| // |
| // Parse bundle symbolic name. |
| // |
| |
| BundleCapabilityImpl bundleCap = parseBundleSymbolicName(owner, m_headerMap); |
| if (bundleCap != null) |
| { |
| m_bundleSymbolicName = (String) |
| bundleCap.getAttributes().get(BundleRevision.BUNDLE_NAMESPACE); |
| |
| // Add a bundle capability and a host capability to all |
| // non-fragment bundles. A host capability is the same |
| // as a require capability, but with a different capability |
| // namespace. Bundle capabilities resolve required-bundle |
| // dependencies, while host capabilities resolve fragment-host |
| // dependencies. |
| if (headerMap.get(Constants.FRAGMENT_HOST) == null) |
| { |
| // All non-fragment bundles have host capabilities. |
| capList.add(bundleCap); |
| // A non-fragment bundle can choose to not have a host capability. |
| String attachment = |
| bundleCap.getDirectives().get(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE); |
| attachment = (attachment == null) |
| ? Constants.FRAGMENT_ATTACHMENT_RESOLVETIME |
| : attachment; |
| if (!attachment.equalsIgnoreCase(Constants.FRAGMENT_ATTACHMENT_NEVER)) |
| { |
| Map<String, Object> hostAttrs = |
| new HashMap<String, Object>(bundleCap.getAttributes()); |
| Object value = hostAttrs.remove(BundleRevision.BUNDLE_NAMESPACE); |
| hostAttrs.put(BundleRevision.HOST_NAMESPACE, value); |
| capList.add(new BundleCapabilityImpl( |
| owner, BundleRevision.HOST_NAMESPACE, |
| Collections.EMPTY_MAP, |
| hostAttrs)); |
| } |
| } |
| |
| // Add a singleton capability if the bundle is a singleton. |
| // This is sort of a hack, but we need this for the resolver |
| // to be able to resolve singletons. It is not possible to |
| // attach this information to the bundle or host capabilities |
| // because fragments don't have those capabilities, but fragments |
| // can be singletons too. |
| if (isSingleton(bundleCap)) |
| { |
| Map<String, Object> singletonAttrs = |
| new HashMap<String, Object>(bundleCap.getAttributes()); |
| Object value = singletonAttrs.remove(BundleRevision.BUNDLE_NAMESPACE); |
| singletonAttrs.put(BundleCapabilityImpl.SINGLETON_NAMESPACE, value); |
| capList.add(new BundleCapabilityImpl( |
| owner, BundleCapabilityImpl.SINGLETON_NAMESPACE, |
| Collections.EMPTY_MAP, |
| singletonAttrs)); |
| } |
| } |
| |
| // Verify that bundle symbolic name is specified. |
| if (getManifestVersion().equals("2") && (m_bundleSymbolicName == null)) |
| { |
| throw new BundleException( |
| "R4 bundle manifests must include bundle symbolic name."); |
| } |
| |
| // |
| // Parse Fragment-Host. |
| // |
| |
| List<BundleRequirementImpl> hostReqs = parseFragmentHost(m_logger, owner, m_headerMap); |
| |
| // |
| // Parse Require-Bundle |
| // |
| |
| List<ParsedHeaderClause> rbClauses = |
| parseStandardHeader((String) headerMap.get(Constants.REQUIRE_BUNDLE)); |
| rbClauses = normalizeRequireClauses(m_logger, rbClauses, getManifestVersion()); |
| List<BundleRequirementImpl> rbReqs = convertRequires(rbClauses, owner); |
| |
| // |
| // Parse Import-Package. |
| // |
| |
| List<ParsedHeaderClause> importClauses = |
| parseStandardHeader((String) headerMap.get(Constants.IMPORT_PACKAGE)); |
| importClauses = normalizeImportClauses(m_logger, importClauses, getManifestVersion()); |
| List<BundleRequirement> importReqs = convertImports(importClauses, owner); |
| |
| // |
| // Parse DynamicImport-Package. |
| // |
| |
| List<ParsedHeaderClause> dynamicClauses = |
| parseStandardHeader((String) headerMap.get(Constants.DYNAMICIMPORT_PACKAGE)); |
| dynamicClauses = normalizeDynamicImportClauses(m_logger, dynamicClauses, getManifestVersion()); |
| List<BundleRequirement> dynamicReqs = convertImports(dynamicClauses, owner); |
| |
| // |
| // Parse Require-Capability. |
| // |
| |
| List<ParsedHeaderClause> requireClauses = |
| parseStandardHeader((String) headerMap.get(Constants.REQUIRE_CAPABILITY)); |
| importClauses = normalizeRequireCapabilityClauses( |
| m_logger, requireClauses, getManifestVersion()); |
| List<BundleRequirement> requireReqs = convertRequireCapabilities(importClauses, owner); |
| |
| // |
| // Parse Export-Package. |
| // |
| |
| List<ParsedHeaderClause> exportClauses = |
| parseStandardHeader((String) headerMap.get(Constants.EXPORT_PACKAGE)); |
| exportClauses = normalizeExportClauses(logger, exportClauses, |
| getManifestVersion(), m_bundleSymbolicName, m_bundleVersion); |
| List<BundleCapability> exportCaps = convertExports(exportClauses, owner); |
| |
| // |
| // Parse Provide-Capability. |
| // |
| |
| List<ParsedHeaderClause> provideClauses = |
| parseStandardHeader((String) headerMap.get(Constants.PROVIDE_CAPABILITY)); |
| exportClauses = normalizeProvideCapabilityClauses( |
| logger, provideClauses, getManifestVersion()); |
| List<BundleCapability> provideCaps = convertProvideCapabilities(provideClauses, owner); |
| |
| // |
| // Calculate implicit imports. |
| // |
| |
| if (!getManifestVersion().equals("2")) |
| { |
| List<ParsedHeaderClause> implicitClauses = |
| calculateImplicitImports(exportCaps, importClauses); |
| importReqs.addAll(convertImports(implicitClauses, owner)); |
| |
| List<ParsedHeaderClause> allImportClauses = |
| new ArrayList<ParsedHeaderClause>(implicitClauses.size() + importClauses.size()); |
| allImportClauses.addAll(importClauses); |
| allImportClauses.addAll(implicitClauses); |
| |
| exportCaps = calculateImplicitUses(exportCaps, allImportClauses); |
| } |
| |
| // Combine all capabilities. |
| m_capabilities = new ArrayList( |
| capList.size() + exportCaps.size() + provideCaps.size()); |
| m_capabilities.addAll(capList); |
| m_capabilities.addAll(exportCaps); |
| m_capabilities.addAll(provideCaps); |
| |
| // Combine all requirements. |
| m_requirements = new ArrayList( |
| importReqs.size() + rbReqs.size() + hostReqs.size() |
| + requireReqs.size() + dynamicReqs.size()); |
| m_requirements.addAll(importReqs); |
| m_requirements.addAll(rbReqs); |
| m_requirements.addAll(hostReqs); |
| m_requirements.addAll(requireReqs); |
| m_requirements.addAll(dynamicReqs); |
| |
| // |
| // Parse Bundle-NativeCode. |
| // |
| |
| // Parse native library clauses. |
| m_libraryClauses = |
| parseLibraryStrings( |
| m_logger, |
| parseDelimitedString((String) m_headerMap.get(Constants.BUNDLE_NATIVECODE), ",")); |
| |
| // Check to see if there was an optional native library clause, which is |
| // represented by a null library header; if so, record it and remove it. |
| if (!m_libraryClauses.isEmpty() && |
| (m_libraryClauses.get(m_libraryClauses.size() - 1).getLibraryEntries() == null)) |
| { |
| m_libraryHeadersOptional = true; |
| m_libraryClauses.remove(m_libraryClauses.size() - 1); |
| } |
| |
| // |
| // Parse activation policy. |
| // |
| |
| // This sets m_activationPolicy, m_includedPolicyClasses, and |
| // m_excludedPolicyClasses. |
| parseActivationPolicy(headerMap); |
| |
| m_isExtension = checkExtensionBundle(headerMap); |
| } |
| |
| private static boolean isSingleton(BundleCapabilityImpl cap) |
| { |
| if (cap.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE)) |
| { |
| String value = cap.getDirectives().get(Constants.SINGLETON_DIRECTIVE); |
| if ((value != null) && Boolean.valueOf(value)) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static List<ParsedHeaderClause> normalizeImportClauses( |
| Logger logger, List<ParsedHeaderClause> clauses, String mv) |
| throws BundleException |
| { |
| // Verify that the values are equals if the package specifies |
| // both version and specification-version attributes. |
| Set dupeSet = new HashSet(); |
| for (ParsedHeaderClause clause : clauses) |
| { |
| // Check for "version" and "specification-version" attributes |
| // and verify they are the same if both are specified. |
| Object v = clause.m_attrs.get(Constants.VERSION_ATTRIBUTE); |
| Object sv = clause.m_attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION); |
| if ((v != null) && (sv != null)) |
| { |
| // Verify they are equal. |
| if (!((String) v).trim().equals(((String) sv).trim())) |
| { |
| throw new IllegalArgumentException( |
| "Both version and specification-version are specified, but they are not equal."); |
| } |
| } |
| |
| // Ensure that only the "version" attribute is used and convert |
| // it to the VersionRange type. |
| if ((v != null) || (sv != null)) |
| { |
| clause.m_attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION); |
| v = (v == null) ? sv : v; |
| clause.m_attrs.put( |
| Constants.VERSION_ATTRIBUTE, |
| VersionRange.parse(v.toString())); |
| } |
| |
| // If bundle version is specified, then convert its type to VersionRange. |
| v = clause.m_attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE); |
| if (v != null) |
| { |
| clause.m_attrs.put( |
| Constants.BUNDLE_VERSION_ATTRIBUTE, |
| VersionRange.parse(v.toString())); |
| } |
| |
| // Verify java.* is not imported, nor any duplicate imports. |
| for (String pkgName : clause.m_paths) |
| { |
| if (!dupeSet.contains(pkgName)) |
| { |
| // Verify that java.* packages are not imported. |
| if (pkgName.startsWith("java.")) |
| { |
| throw new BundleException( |
| "Importing java.* packages not allowed: " + pkgName); |
| } |
| // Make sure a package name was specified. |
| else if (pkgName.length() == 0) |
| { |
| throw new BundleException( |
| "Imported package names cannot be zero length."); |
| } |
| dupeSet.add(pkgName); |
| } |
| else |
| { |
| throw new BundleException("Duplicate import: " + pkgName); |
| } |
| } |
| |
| if (!mv.equals("2")) |
| { |
| // R3 bundles cannot have directives on their imports. |
| if (!clause.m_dirs.isEmpty()) |
| { |
| throw new BundleException("R3 imports cannot contain directives."); |
| } |
| |
| // Remove and ignore all attributes other than version. |
| // NOTE: This is checking for "version" rather than "specification-version" |
| // because the package class normalizes to "version" to avoid having |
| // future special cases. This could be changed if more strict behavior |
| // is required. |
| if (!clause.m_attrs.isEmpty()) |
| { |
| // R3 package requirements should only have version attributes. |
| Object pkgVersion = clause.m_attrs.get(BundleCapabilityImpl.VERSION_ATTR); |
| pkgVersion = (pkgVersion == null) |
| ? new VersionRange(Version.emptyVersion, true, null, true) |
| : pkgVersion; |
| for (Entry<String, Object> entry : clause.m_attrs.entrySet()) |
| { |
| if (!entry.getKey().equals(BundleCapabilityImpl.VERSION_ATTR)) |
| { |
| logger.log(Logger.LOG_WARNING, |
| "Unknown R3 import attribute: " |
| + entry.getKey()); |
| } |
| } |
| |
| // Remove all other attributes except package version. |
| clause.m_attrs.clear(); |
| clause.m_attrs.put(BundleCapabilityImpl.VERSION_ATTR, pkgVersion); |
| } |
| } |
| } |
| |
| return clauses; |
| } |
| |
| public static List<BundleRequirement> parseDynamicImportHeader( |
| Logger logger, BundleRevision owner, String header) |
| throws BundleException |
| { |
| |
| List<ParsedHeaderClause> importClauses = parseStandardHeader(header); |
| importClauses = normalizeDynamicImportClauses(logger, importClauses, "2"); |
| List<BundleRequirement> reqs = convertImports(importClauses, owner); |
| return reqs; |
| } |
| |
| private static List<BundleRequirement> convertImports( |
| List<ParsedHeaderClause> clauses, BundleRevision owner) |
| { |
| // Now convert generic header clauses into requirements. |
| List reqList = new ArrayList(); |
| for (ParsedHeaderClause clause : clauses) |
| { |
| for (String path : clause.m_paths) |
| { |
| // Prepend the package name to the array of attributes. |
| Map<String, Object> attrs = clause.m_attrs; |
| // Note that we use a linked hash map here to ensure the |
| // package attribute is first, which will make indexing |
| // more efficient. |
| // TODO: OSGi R4.3 - This ordering fix is a hack...perhaps we should use the standard "key" |
| // notion where namespace is also the name of the key attribute. |
| Map<String, Object> newAttrs = new LinkedHashMap<String, Object>(attrs.size() + 1); |
| newAttrs.put( |
| BundleRevision.PACKAGE_NAMESPACE, |
| path); |
| newAttrs.putAll(attrs); |
| |
| // Create filter now so we can inject filter directive. |
| SimpleFilter sf = BundleRequirementImpl.convertToFilter(newAttrs); |
| |
| // Inject filter directive. |
| // TODO: OSGi R4.3 - Can we insert this on demand somehow? |
| Map<String, String> dirs = clause.m_dirs; |
| Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1); |
| newDirs.putAll(dirs); |
| newDirs.put( |
| Constants.FILTER_DIRECTIVE, |
| sf.toString()); |
| |
| // Create package requirement and add to requirement list. |
| reqList.add( |
| new BundleRequirementImpl( |
| owner, |
| BundleRevision.PACKAGE_NAMESPACE, |
| newDirs, |
| Collections.EMPTY_MAP, |
| sf)); |
| } |
| } |
| |
| return reqList; |
| } |
| |
| private static List<ParsedHeaderClause> normalizeDynamicImportClauses( |
| Logger logger, List<ParsedHeaderClause> clauses, String mv) |
| throws BundleException |
| { |
| // Verify that the values are equals if the package specifies |
| // both version and specification-version attributes. |
| for (ParsedHeaderClause clause : clauses) |
| { |
| if (!mv.equals("2")) |
| { |
| // R3 bundles cannot have directives on their imports. |
| if (!clause.m_dirs.isEmpty()) |
| { |
| throw new BundleException("R3 imports cannot contain directives."); |
| } |
| } |
| |
| // Add the resolution directive to indicate that these are |
| // dynamic imports. |
| // TODO: OSGi R4.3 - Use real constant value for "dynamic". |
| clause.m_dirs.put(Constants.RESOLUTION_DIRECTIVE, "dynamic"); |
| |
| // Check for "version" and "specification-version" attributes |
| // and verify they are the same if both are specified. |
| Object v = clause.m_attrs.get(Constants.VERSION_ATTRIBUTE); |
| Object sv = clause.m_attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION); |
| if ((v != null) && (sv != null)) |
| { |
| // Verify they are equal. |
| if (!((String) v).trim().equals(((String) sv).trim())) |
| { |
| throw new IllegalArgumentException( |
| "Both version and specification-version are specified, but they are not equal."); |
| } |
| } |
| |
| // Ensure that only the "version" attribute is used and convert |
| // it to the VersionRange type. |
| if ((v != null) || (sv != null)) |
| { |
| clause.m_attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION); |
| v = (v == null) ? sv : v; |
| clause.m_attrs.put( |
| Constants.VERSION_ATTRIBUTE, |
| VersionRange.parse(v.toString())); |
| } |
| |
| // If bundle version is specified, then convert its type to VersionRange. |
| v = clause.m_attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE); |
| if (v != null) |
| { |
| clause.m_attrs.put( |
| Constants.BUNDLE_VERSION_ATTRIBUTE, |
| VersionRange.parse(v.toString())); |
| } |
| |
| // Dynamic imports can have duplicates, so verify that java.* |
| // packages are not imported. |
| for (String pkgName : clause.m_paths) |
| { |
| if (pkgName.startsWith("java.")) |
| { |
| throw new BundleException( |
| "Dynamically importing java.* packages not allowed: " + pkgName); |
| } |
| else if (!pkgName.equals("*") && pkgName.endsWith("*") && !pkgName.endsWith(".*")) |
| { |
| throw new BundleException( |
| "Partial package name wild carding is not allowed: " + pkgName); |
| } |
| } |
| } |
| |
| return clauses; |
| } |
| |
| private static List<ParsedHeaderClause> normalizeRequireCapabilityClauses( |
| Logger logger, List<ParsedHeaderClause> clauses, String mv) |
| throws BundleException |
| { |
| |
| if (!mv.equals("2") && !clauses.isEmpty()) |
| { |
| // Should we error here if we are not an R4 bundle? |
| } |
| |
| return clauses; |
| } |
| |
| private static List<BundleRequirement> convertRequireCapabilities( |
| List<ParsedHeaderClause> clauses, BundleRevision owner) |
| throws BundleException |
| { |
| // Now convert generic header clauses into requirements. |
| List reqList = new ArrayList(); |
| for (ParsedHeaderClause clause : clauses) |
| { |
| try |
| { |
| String filterStr = clause.m_dirs.get(Constants.FILTER_DIRECTIVE); |
| SimpleFilter sf = (filterStr != null) |
| ? SimpleFilter.parse(filterStr) |
| : new SimpleFilter(null, null, SimpleFilter.MATCH_ALL); |
| for (String path : clause.m_paths) |
| { |
| // Create requirement and add to requirement list. |
| reqList.add( |
| new BundleRequirementImpl( |
| owner, |
| path, |
| clause.m_dirs, |
| clause.m_attrs, |
| sf)); |
| } |
| } |
| catch (Exception ex) |
| { |
| throw new BundleException("Error creating requirement: " + ex); |
| } |
| } |
| |
| return reqList; |
| } |
| |
| private static List<ParsedHeaderClause> normalizeProvideCapabilityClauses( |
| Logger logger, List<ParsedHeaderClause> clauses, String mv) |
| throws BundleException |
| { |
| |
| if (!mv.equals("2") && !clauses.isEmpty()) |
| { |
| // Should we error here if we are not an R4 bundle? |
| } |
| |
| // Convert attributes into specified types. |
| for (ParsedHeaderClause clause : clauses) |
| { |
| for (Entry<String, String> entry : clause.m_types.entrySet()) |
| { |
| String type = entry.getValue(); |
| if (!type.equals("String")) |
| { |
| if (type.equals("Double")) |
| { |
| clause.m_attrs.put( |
| entry.getKey(), |
| new Double(clause.m_attrs.get(entry.getKey()).toString().trim())); |
| } |
| else if (type.equals("Version")) |
| { |
| clause.m_attrs.put( |
| entry.getKey(), |
| new Version(clause.m_attrs.get(entry.getKey()).toString().trim())); |
| } |
| else if (type.equals("Long")) |
| { |
| clause.m_attrs.put( |
| entry.getKey(), |
| new Long(clause.m_attrs.get(entry.getKey()).toString().trim())); |
| } |
| else if (type.startsWith("List")) |
| { |
| int startIdx = type.indexOf('<'); |
| int endIdx = type.indexOf('>'); |
| if (((startIdx > 0) && (endIdx <= startIdx)) |
| || ((startIdx < 0) && (endIdx > 0))) |
| { |
| throw new BundleException( |
| "Invalid Provide-Capability attribute list type for '" |
| + entry.getKey() |
| + "' : " |
| + type); |
| } |
| |
| String listType = "String"; |
| if (endIdx > startIdx) |
| { |
| listType = type.substring(startIdx + 1, endIdx).trim(); |
| } |
| |
| List<String> tokens = parseDelimitedString( |
| clause.m_attrs.get(entry.getKey()).toString(), ",", false); |
| List<Object> values = new ArrayList<Object>(tokens.size()); |
| for (String token : tokens) |
| { |
| if (listType.equals("String")) |
| { |
| values.add(token); |
| } |
| else if (listType.equals("Double")) |
| { |
| values.add(new Double(token.trim())); |
| } |
| else if (listType.equals("Version")) |
| { |
| values.add(new Version(token.trim())); |
| } |
| else if (listType.equals("Long")) |
| { |
| values.add(new Long(token.trim())); |
| } |
| else |
| { |
| throw new BundleException( |
| "Unknown Provide-Capability attribute list type for '" |
| + entry.getKey() |
| + "' : " |
| + type); |
| } |
| } |
| clause.m_attrs.put( |
| entry.getKey(), |
| values); |
| } |
| else |
| { |
| throw new BundleException( |
| "Unknown Provide-Capability attribute type for '" |
| + entry.getKey() |
| + "' : " |
| + type); |
| } |
| } |
| } |
| } |
| |
| return clauses; |
| } |
| |
| private static List<BundleCapability> convertProvideCapabilities( |
| List<ParsedHeaderClause> clauses, BundleRevision owner) |
| { |
| List<BundleCapability> capList = new ArrayList(); |
| for (ParsedHeaderClause clause : clauses) |
| { |
| for (String path : clause.m_paths) |
| { |
| // Create package capability and add to capability list. |
| capList.add( |
| new BundleCapabilityImpl( |
| owner, |
| path, |
| clause.m_dirs, |
| clause.m_attrs)); |
| } |
| } |
| |
| return capList; |
| } |
| |
| private static List<ParsedHeaderClause> normalizeExportClauses( |
| Logger logger, List<ParsedHeaderClause> clauses, |
| String mv, String bsn, Version bv) |
| throws BundleException |
| { |
| // Verify that "java.*" packages are not exported. |
| for (ParsedHeaderClause clause : clauses) |
| { |
| // Verify that the named package has not already been declared. |
| for (String pkgName : clause.m_paths) |
| { |
| // Verify that java.* packages are not exported. |
| if (pkgName.startsWith("java.")) |
| { |
| throw new BundleException( |
| "Exporting java.* packages not allowed: " |
| + pkgName); |
| } |
| else if (pkgName.length() == 0) |
| { |
| throw new BundleException( |
| "Exported package names cannot be zero length."); |
| } |
| } |
| |
| // Check for "version" and "specification-version" attributes |
| // and verify they are the same if both are specified. |
| Object v = clause.m_attrs.get(Constants.VERSION_ATTRIBUTE); |
| Object sv = clause.m_attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION); |
| if ((v != null) && (sv != null)) |
| { |
| // Verify they are equal. |
| if (!((String) v).trim().equals(((String) sv).trim())) |
| { |
| throw new IllegalArgumentException( |
| "Both version and specification-version are specified, but they are not equal."); |
| } |
| } |
| |
| // Always add the default version if not specified. |
| if ((v == null) && (sv == null)) |
| { |
| v = Version.emptyVersion; |
| } |
| |
| // Ensure that only the "version" attribute is used and convert |
| // it to the appropriate type. |
| if ((v != null) || (sv != null)) |
| { |
| // Convert version attribute to type Version. |
| clause.m_attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION); |
| v = (v == null) ? sv : v; |
| clause.m_attrs.put( |
| Constants.VERSION_ATTRIBUTE, |
| Version.parseVersion(v.toString())); |
| } |
| |
| // If this is an R4 bundle, then make sure it doesn't specify |
| // bundle symbolic name or bundle version attributes. |
| if (mv.equals("2")) |
| { |
| // Find symbolic name and version attribute, if present. |
| if (clause.m_attrs.containsKey(Constants.BUNDLE_VERSION_ATTRIBUTE) |
| || clause.m_attrs.containsKey(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE)) |
| { |
| throw new BundleException( |
| "Exports must not specify bundle symbolic name or bundle version."); |
| } |
| |
| // Now that we know that there are no bundle symbolic name and version |
| // attributes, add them since the spec says they are there implicitly. |
| clause.m_attrs.put(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, bsn); |
| clause.m_attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, bv); |
| } |
| else if (!mv.equals("2")) |
| { |
| // R3 bundles cannot have directives on their exports. |
| if (!clause.m_dirs.isEmpty()) |
| { |
| throw new BundleException("R3 exports cannot contain directives."); |
| } |
| |
| // Remove and ignore all attributes other than version. |
| // NOTE: This is checking for "version" rather than "specification-version" |
| // because the package class normalizes to "version" to avoid having |
| // future special cases. This could be changed if more strict behavior |
| // is required. |
| if (!clause.m_attrs.isEmpty()) |
| { |
| // R3 package capabilities should only have a version attribute. |
| Object pkgVersion = clause.m_attrs.get(BundleCapabilityImpl.VERSION_ATTR); |
| pkgVersion = (pkgVersion == null) |
| ? Version.emptyVersion |
| : pkgVersion; |
| for (Entry<String, Object> entry : clause.m_attrs.entrySet()) |
| { |
| if (!entry.getKey().equals(BundleCapabilityImpl.VERSION_ATTR)) |
| { |
| logger.log( |
| Logger.LOG_WARNING, |
| "Unknown R3 export attribute: " |
| + entry.getKey()); |
| } |
| } |
| |
| // Remove all other attributes except package version. |
| clause.m_attrs.clear(); |
| clause.m_attrs.put(BundleCapabilityImpl.VERSION_ATTR, pkgVersion); |
| } |
| } |
| } |
| |
| return clauses; |
| } |
| |
| private static List<BundleCapability> convertExports( |
| List<ParsedHeaderClause> clauses, BundleRevision owner) |
| { |
| List<BundleCapability> capList = new ArrayList(); |
| for (ParsedHeaderClause clause : clauses) |
| { |
| for (String pkgName : clause.m_paths) |
| { |
| // Prepend the package name to the array of attributes. |
| Map<String, Object> attrs = clause.m_attrs; |
| Map<String, Object> newAttrs = new HashMap<String, Object>(attrs.size() + 1); |
| newAttrs.putAll(attrs); |
| newAttrs.put( |
| BundleRevision.PACKAGE_NAMESPACE, |
| pkgName); |
| |
| // Create package capability and add to capability list. |
| capList.add( |
| new BundleCapabilityImpl( |
| owner, |
| BundleRevision.PACKAGE_NAMESPACE, |
| clause.m_dirs, |
| newAttrs)); |
| } |
| } |
| |
| return capList; |
| } |
| |
| public String getManifestVersion() |
| { |
| String manifestVersion = getManifestVersion(m_headerMap); |
| return (manifestVersion == null) ? "1" : manifestVersion; |
| } |
| |
| private static String getManifestVersion(Map headerMap) |
| { |
| String manifestVersion = (String) headerMap.get(Constants.BUNDLE_MANIFESTVERSION); |
| return (manifestVersion == null) ? null : manifestVersion.trim(); |
| } |
| |
| public int getActivationPolicy() |
| { |
| return m_activationPolicy; |
| } |
| |
| public String getActivationIncludeDirective() |
| { |
| return m_activationIncludeDir; |
| } |
| |
| public String getActivationExcludeDirective() |
| { |
| return m_activationExcludeDir; |
| } |
| |
| public boolean isExtension() |
| { |
| return m_isExtension; |
| } |
| |
| public String getSymbolicName() |
| { |
| return m_bundleSymbolicName; |
| } |
| |
| public Version getBundleVersion() |
| { |
| return m_bundleVersion; |
| } |
| |
| public List<BundleCapability> getCapabilities() |
| { |
| return m_capabilities; |
| } |
| |
| public List<BundleRequirement> getRequirements() |
| { |
| return m_requirements; |
| } |
| |
| public List<R4LibraryClause> getLibraryClauses() |
| { |
| return m_libraryClauses; |
| } |
| |
| /** |
| * <p> |
| * This method returns the selected native library metadata from |
| * the manifest. The information is not the raw metadata from the |
| * manifest, but is the native library clause selected according |
| * to the OSGi native library clause selection policy. The metadata |
| * returned by this method will be attached directly to a module and |
| * used for finding its native libraries at run time. To inspect the |
| * raw native library metadata refer to <tt>getLibraryClauses()</tt>. |
| * </p> |
| * <p> |
| * This method returns one of three values: |
| * </p> |
| * <ul> |
| * <li><tt>null</tt> - if the are no native libraries for this module; |
| * this may also indicate the native libraries are optional and |
| * did not match the current platform.</li> |
| * <li>Zero-length <tt>R4Library</tt> array - if no matching native library |
| * clause was found; this bundle should not resolve.</li> |
| * <li>Nonzero-length <tt>R4Library</tt> array - the native libraries |
| * associated with the matching native library clause.</li> |
| * </ul> |
| * |
| * @return <tt>null</tt> if there are no native libraries, a zero-length |
| * array if no libraries matched, or an array of selected libraries. |
| **/ |
| public List<R4Library> getLibraries() |
| { |
| ArrayList<R4Library> libs = null; |
| try |
| { |
| R4LibraryClause clause = getSelectedLibraryClause(); |
| if (clause != null) |
| { |
| String[] entries = clause.getLibraryEntries(); |
| libs = new ArrayList<R4Library>(entries.length); |
| int current = 0; |
| for (int i = 0; i < entries.length; i++) |
| { |
| String name = getName(entries[i]); |
| boolean found = false; |
| for (int j = 0; !found && (j < current); j++) |
| { |
| found = getName(entries[j]).equals(name); |
| } |
| if (!found) |
| { |
| libs.add(new R4Library( |
| clause.getLibraryEntries()[i], |
| clause.getOSNames(), clause.getProcessors(), clause.getOSVersions(), |
| clause.getLanguages(), clause.getSelectionFilter())); |
| } |
| } |
| libs.trimToSize(); |
| } |
| } |
| catch (Exception ex) |
| { |
| libs = new ArrayList<R4Library>(0); |
| } |
| return libs; |
| } |
| |
| private String getName(String path) |
| { |
| int idx = path.lastIndexOf('/'); |
| if (idx > -1) |
| { |
| return path.substring(idx); |
| } |
| return path; |
| } |
| |
| private R4LibraryClause getSelectedLibraryClause() throws BundleException |
| { |
| if ((m_libraryClauses != null) && (m_libraryClauses.size() > 0)) |
| { |
| List clauseList = new ArrayList(); |
| |
| // Search for matching native clauses. |
| for (R4LibraryClause libraryClause : m_libraryClauses) |
| { |
| if (libraryClause.match(m_configMap)) |
| { |
| clauseList.add(libraryClause); |
| } |
| } |
| |
| // Select the matching native clause. |
| int selected = 0; |
| if (clauseList.isEmpty()) |
| { |
| // If optional clause exists, no error thrown. |
| if (m_libraryHeadersOptional) |
| { |
| return null; |
| } |
| else |
| { |
| throw new BundleException("Unable to select a native library clause."); |
| } |
| } |
| else if (clauseList.size() == 1) |
| { |
| selected = 0; |
| } |
| else if (clauseList.size() > 1) |
| { |
| selected = firstSortedClause(clauseList); |
| } |
| return ((R4LibraryClause) clauseList.get(selected)); |
| } |
| |
| return null; |
| } |
| |
| private int firstSortedClause(List<R4LibraryClause> clauseList) |
| { |
| ArrayList indexList = new ArrayList(); |
| ArrayList selection = new ArrayList(); |
| |
| // Init index list |
| for (int i = 0; i < clauseList.size(); i++) |
| { |
| indexList.add("" + i); |
| } |
| |
| // Select clause with 'osversion' range declared |
| // and get back the max floor of 'osversion' ranges. |
| Version osVersionRangeMaxFloor = new Version(0, 0, 0); |
| for (int i = 0; i < indexList.size(); i++) |
| { |
| int index = Integer.parseInt(indexList.get(i).toString()); |
| String[] osversions = ((R4LibraryClause) clauseList.get(index)).getOSVersions(); |
| if (osversions != null) |
| { |
| selection.add("" + indexList.get(i)); |
| } |
| for (int k = 0; (osversions != null) && (k < osversions.length); k++) |
| { |
| VersionRange range = VersionRange.parse(osversions[k]); |
| if ((range.getFloor()).compareTo(osVersionRangeMaxFloor) >= 0) |
| { |
| osVersionRangeMaxFloor = range.getFloor(); |
| } |
| } |
| } |
| |
| if (selection.size() == 1) |
| { |
| return Integer.parseInt(selection.get(0).toString()); |
| } |
| else if (selection.size() > 1) |
| { |
| // Keep only selected clauses with an 'osversion' |
| // equal to the max floor of 'osversion' ranges. |
| indexList = selection; |
| selection = new ArrayList(); |
| for (int i = 0; i < indexList.size(); i++) |
| { |
| int index = Integer.parseInt(indexList.get(i).toString()); |
| String[] osversions = ((R4LibraryClause) clauseList.get(index)).getOSVersions(); |
| for (int k = 0; k < osversions.length; k++) |
| { |
| VersionRange range = VersionRange.parse(osversions[k]); |
| if ((range.getFloor()).compareTo(osVersionRangeMaxFloor) >= 0) |
| { |
| selection.add("" + indexList.get(i)); |
| } |
| } |
| } |
| } |
| |
| if (selection.isEmpty()) |
| { |
| // Re-init index list. |
| selection.clear(); |
| indexList.clear(); |
| for (int i = 0; i < clauseList.size(); i++) |
| { |
| indexList.add("" + i); |
| } |
| } |
| else if (selection.size() == 1) |
| { |
| return Integer.parseInt(selection.get(0).toString()); |
| } |
| else |
| { |
| indexList = selection; |
| selection.clear(); |
| } |
| |
| // Keep only clauses with 'language' declared. |
| for (int i = 0; i < indexList.size(); i++) |
| { |
| int index = Integer.parseInt(indexList.get(i).toString()); |
| if (((R4LibraryClause) clauseList.get(index)).getLanguages() != null) |
| { |
| selection.add("" + indexList.get(i)); |
| } |
| } |
| |
| // Return the first sorted clause |
| if (selection.isEmpty()) |
| { |
| return 0; |
| } |
| else |
| { |
| return Integer.parseInt(selection.get(0).toString()); |
| } |
| } |
| |
| private static List<ParsedHeaderClause> calculateImplicitImports( |
| List<BundleCapability> exports, List<ParsedHeaderClause> imports) |
| throws BundleException |
| { |
| List<ParsedHeaderClause> clauseList = new ArrayList(); |
| |
| // Since all R3 exports imply an import, add a corresponding |
| // requirement for each existing export capability. Do not |
| // duplicate imports. |
| Map map = new HashMap(); |
| // Add existing imports. |
| for (int impIdx = 0; impIdx < imports.size(); impIdx++) |
| { |
| for (int pathIdx = 0; pathIdx < imports.get(impIdx).m_paths.size(); pathIdx++) |
| { |
| map.put( |
| imports.get(impIdx).m_paths.get(pathIdx), |
| imports.get(impIdx).m_paths.get(pathIdx)); |
| } |
| } |
| // Add import requirement for each export capability. |
| for (int i = 0; i < exports.size(); i++) |
| { |
| if (map.get(exports.get(i).getAttributes() |
| .get(BundleRevision.PACKAGE_NAMESPACE)) == null) |
| { |
| // Convert Version to VersionRange. |
| Map<String, Object> attrs = new HashMap<String, Object>(); |
| Object version = exports.get(i).getAttributes().get(Constants.VERSION_ATTRIBUTE); |
| if (version != null) |
| { |
| attrs.put( |
| Constants.VERSION_ATTRIBUTE, |
| VersionRange.parse(version.toString())); |
| } |
| |
| List<String> paths = new ArrayList(); |
| paths.add((String) |
| exports.get(i).getAttributes().get(BundleRevision.PACKAGE_NAMESPACE)); |
| clauseList.add( |
| new ParsedHeaderClause( |
| paths, Collections.EMPTY_MAP, attrs, Collections.EMPTY_MAP)); |
| } |
| } |
| |
| return clauseList; |
| } |
| |
| private static List<BundleCapability> calculateImplicitUses( |
| List<BundleCapability> exports, List<ParsedHeaderClause> imports) |
| throws BundleException |
| { |
| // Add a "uses" directive onto each export of R3 bundles |
| // that references every other import (which will include |
| // exports, since export implies import); this is |
| // necessary since R3 bundles assumed a single class space, |
| // but R4 allows for multiple class spaces. |
| String usesValue = ""; |
| for (int i = 0; i < imports.size(); i++) |
| { |
| for (int pathIdx = 0; pathIdx < imports.get(i).m_paths.size(); pathIdx++) |
| { |
| usesValue = usesValue |
| + ((usesValue.length() > 0) ? "," : "") |
| + imports.get(i).m_paths.get(pathIdx); |
| } |
| } |
| for (int i = 0; i < exports.size(); i++) |
| { |
| Map<String, String> dirs = new HashMap<String, String>(1); |
| dirs.put(Constants.USES_DIRECTIVE, usesValue); |
| exports.set(i, new BundleCapabilityImpl( |
| exports.get(i).getRevision(), |
| BundleRevision.PACKAGE_NAMESPACE, |
| dirs, |
| exports.get(i).getAttributes())); |
| } |
| |
| return exports; |
| } |
| |
| private static boolean checkExtensionBundle(Map headerMap) throws BundleException |
| { |
| Object extension = parseExtensionBundleHeader( |
| (String) headerMap.get(Constants.FRAGMENT_HOST)); |
| |
| if (extension != null) |
| { |
| if (!(Constants.EXTENSION_FRAMEWORK.equals(extension) || |
| Constants.EXTENSION_BOOTCLASSPATH.equals(extension))) |
| { |
| throw new BundleException( |
| "Extension bundle must have either 'extension:=framework' or 'extension:=bootclasspath'"); |
| } |
| if (headerMap.containsKey(Constants.IMPORT_PACKAGE) || |
| headerMap.containsKey(Constants.REQUIRE_BUNDLE) || |
| headerMap.containsKey(Constants.BUNDLE_NATIVECODE) || |
| headerMap.containsKey(Constants.DYNAMICIMPORT_PACKAGE) || |
| headerMap.containsKey(Constants.BUNDLE_ACTIVATOR)) |
| { |
| throw new BundleException("Invalid extension bundle manifest"); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| private static BundleCapabilityImpl parseBundleSymbolicName( |
| BundleRevision owner, Map headerMap) |
| throws BundleException |
| { |
| List<ParsedHeaderClause> clauses = parseStandardHeader( |
| (String) headerMap.get(Constants.BUNDLE_SYMBOLICNAME)); |
| if (clauses.size() > 0) |
| { |
| if (clauses.size() > 1) |
| { |
| throw new BundleException( |
| "Cannot have multiple symbolic names: " |
| + headerMap.get(Constants.BUNDLE_SYMBOLICNAME)); |
| } |
| else if (clauses.get(0).m_paths.size() > 1) |
| { |
| throw new BundleException( |
| "Cannot have multiple symbolic names: " |
| + headerMap.get(Constants.BUNDLE_SYMBOLICNAME)); |
| } |
| |
| // Get bundle version. |
| Version bundleVersion = Version.emptyVersion; |
| if (headerMap.get(Constants.BUNDLE_VERSION) != null) |
| { |
| try |
| { |
| bundleVersion = Version.parseVersion( |
| (String) headerMap.get(Constants.BUNDLE_VERSION)); |
| } |
| catch (RuntimeException ex) |
| { |
| // R4 bundle versions must parse, R3 bundle version may not. |
| String mv = getManifestVersion(headerMap); |
| if (mv != null) |
| { |
| throw ex; |
| } |
| bundleVersion = Version.emptyVersion; |
| } |
| } |
| |
| // Create a require capability and return it. |
| String symName = (String) clauses.get(0).m_paths.get(0); |
| clauses.get(0).m_attrs.put(BundleRevision.BUNDLE_NAMESPACE, symName); |
| clauses.get(0).m_attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, bundleVersion); |
| return new BundleCapabilityImpl( |
| owner, |
| BundleRevision.BUNDLE_NAMESPACE, |
| clauses.get(0).m_dirs, |
| clauses.get(0).m_attrs); |
| } |
| |
| return null; |
| } |
| |
| private static List<BundleRequirementImpl> parseFragmentHost( |
| Logger logger, BundleRevision owner, Map headerMap) |
| throws BundleException |
| { |
| List<BundleRequirementImpl> reqs = new ArrayList(); |
| |
| String mv = getManifestVersion(headerMap); |
| if ((mv != null) && mv.equals("2")) |
| { |
| List<ParsedHeaderClause> clauses = parseStandardHeader( |
| (String) headerMap.get(Constants.FRAGMENT_HOST)); |
| if (clauses.size() > 0) |
| { |
| // Make sure that only one fragment host symbolic name is specified. |
| if (clauses.size() > 1) |
| { |
| throw new BundleException( |
| "Fragments cannot have multiple hosts: " |
| + headerMap.get(Constants.FRAGMENT_HOST)); |
| } |
| else if (clauses.get(0).m_paths.size() > 1) |
| { |
| throw new BundleException( |
| "Fragments cannot have multiple hosts: " |
| + headerMap.get(Constants.FRAGMENT_HOST)); |
| } |
| |
| // Strip all attributes other than bundle-version. |
| for (Iterator<Entry<String, Object>> it = |
| clauses.get(0).m_attrs.entrySet().iterator(); |
| it.hasNext(); ) |
| { |
| Entry<String, Object> entry = it.next(); |
| if (!entry.getKey().equals(Constants.BUNDLE_VERSION_ATTRIBUTE)) |
| { |
| it.remove(); |
| } |
| } |
| |
| // If the bundle-version attribute is specified, then convert |
| // it to the proper type. |
| Object value = clauses.get(0).m_attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE); |
| if (value != null) |
| { |
| clauses.get(0).m_attrs.put( |
| Constants.BUNDLE_VERSION_ATTRIBUTE, |
| VersionRange.parse(value.toString())); |
| } |
| |
| // Prepend the host symbolic name to the array of attributes. |
| Map<String, Object> attrs = clauses.get(0).m_attrs; |
| Map<String, Object> newAttrs = new HashMap<String, Object>(attrs.size() + 1); |
| newAttrs.putAll(attrs); |
| newAttrs.put( |
| BundleRevision.HOST_NAMESPACE, |
| clauses.get(0).m_paths.get(0)); |
| |
| // Create filter now so we can inject filter directive. |
| SimpleFilter sf = BundleRequirementImpl.convertToFilter(newAttrs); |
| |
| // Inject filter directive. |
| // TODO: OSGi R4.3 - Can we insert this on demand somehow? |
| Map<String, String> dirs = clauses.get(0).m_dirs; |
| Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1); |
| newDirs.putAll(dirs); |
| newDirs.put( |
| Constants.FILTER_DIRECTIVE, |
| sf.toString()); |
| |
| reqs.add(new BundleRequirementImpl( |
| owner, BundleRevision.HOST_NAMESPACE, |
| newDirs, |
| newAttrs)); |
| } |
| } |
| else if (headerMap.get(Constants.FRAGMENT_HOST) != null) |
| { |
| String s = (String) headerMap.get(Constants.BUNDLE_SYMBOLICNAME); |
| s = (s == null) ? (String) headerMap.get(Constants.BUNDLE_NAME) : s; |
| s = (s == null) ? headerMap.toString() : s; |
| logger.log( |
| owner.getBundle(), |
| Logger.LOG_WARNING, |
| "Only R4 bundles can be fragments: " + s); |
| } |
| |
| return reqs; |
| } |
| |
| public static List<BundleCapability> parseExportHeader( |
| Logger logger, BundleRevision owner, String header, String bsn, Version bv) |
| { |
| |
| List<BundleCapability> caps = null; |
| try |
| { |
| List<ParsedHeaderClause> exportClauses = parseStandardHeader(header); |
| exportClauses = normalizeExportClauses(logger, exportClauses, "2", bsn, bv); |
| caps = convertExports(exportClauses, owner); |
| } |
| catch (BundleException ex) |
| { |
| caps = null; |
| } |
| return caps; |
| } |
| |
| private static List<ParsedHeaderClause> normalizeRequireClauses( |
| Logger logger, List<ParsedHeaderClause> clauses, String mv) |
| { |
| // R3 bundles cannot require other bundles. |
| if (!mv.equals("2")) |
| { |
| clauses.clear(); |
| } |
| else |
| { |
| // Convert bundle version attribute to VersionRange type. |
| for (ParsedHeaderClause clause : clauses) |
| { |
| Object value = clause.m_attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE); |
| if (value != null) |
| { |
| clause.m_attrs.put( |
| Constants.BUNDLE_VERSION_ATTRIBUTE, |
| VersionRange.parse(value.toString())); |
| } |
| } |
| } |
| |
| return clauses; |
| } |
| |
| private static List<BundleRequirementImpl> convertRequires( |
| List<ParsedHeaderClause> clauses, BundleRevision owner) |
| { |
| List<BundleRequirementImpl> reqList = new ArrayList(); |
| for (ParsedHeaderClause clause : clauses) |
| { |
| for (String path : clause.m_paths) |
| { |
| // Prepend the bundle symbolic name to the array of attributes. |
| Map<String, Object> attrs = clause.m_attrs; |
| // Note that we use a linked hash map here to ensure the |
| // package attribute is first, which will make indexing |
| // more efficient. |
| // TODO: OSGi R4.3 - This ordering fix is a hack...perhaps we should use the standard "key" |
| // notion where namespace is also the name of the key attribute. |
| // Prepend the symbolic name to the array of attributes. |
| Map<String, Object> newAttrs = new LinkedHashMap<String, Object>(attrs.size() + 1); |
| newAttrs.put( |
| BundleRevision.BUNDLE_NAMESPACE, |
| path); |
| newAttrs.putAll(attrs); |
| |
| // Create filter now so we can inject filter directive. |
| SimpleFilter sf = BundleRequirementImpl.convertToFilter(newAttrs); |
| |
| // Inject filter directive. |
| // TODO: OSGi R4.3 - Can we insert this on demand somehow? |
| Map<String, String> dirs = clause.m_dirs; |
| Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1); |
| newDirs.putAll(dirs); |
| newDirs.put( |
| Constants.FILTER_DIRECTIVE, |
| sf.toString()); |
| |
| // Create package requirement and add to requirement list. |
| reqList.add( |
| new BundleRequirementImpl( |
| owner, |
| BundleRevision.BUNDLE_NAMESPACE, |
| newDirs, |
| newAttrs)); |
| } |
| } |
| |
| return reqList; |
| } |
| |
| public static String parseExtensionBundleHeader(String header) |
| throws BundleException |
| { |
| List<ParsedHeaderClause> clauses = parseStandardHeader(header); |
| |
| String result = null; |
| |
| if (clauses.size() == 1) |
| { |
| // See if there is the "extension" directive. |
| for (Entry<String, String> entry : clauses.get(0).m_dirs.entrySet()) |
| { |
| if (Constants.EXTENSION_DIRECTIVE.equals(entry.getKey())) |
| { |
| // If the extension directive is specified, make sure |
| // the target is the system bundle. |
| if (FelixConstants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(clauses.get(0).m_paths.get(0)) || |
| Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(clauses.get(0).m_paths.get(0))) |
| { |
| return entry.getValue(); |
| } |
| else |
| { |
| throw new BundleException( |
| "Only the system bundle can have extension bundles."); |
| } |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| private void parseActivationPolicy(Map headerMap) |
| { |
| m_activationPolicy = BundleRevisionImpl.EAGER_ACTIVATION; |
| |
| List<ParsedHeaderClause> clauses = parseStandardHeader( |
| (String) headerMap.get(Constants.BUNDLE_ACTIVATIONPOLICY)); |
| |
| if (clauses.size() > 0) |
| { |
| // Just look for a "path" matching the lazy policy, ignore |
| // everything else. |
| for (String path : clauses.get(0).m_paths) |
| { |
| if (path.equals(Constants.ACTIVATION_LAZY)) |
| { |
| m_activationPolicy = BundleRevisionImpl.LAZY_ACTIVATION; |
| for (Entry<String, String> entry : clauses.get(0).m_dirs.entrySet()) |
| { |
| if (entry.getKey().equalsIgnoreCase(Constants.INCLUDE_DIRECTIVE)) |
| { |
| m_activationIncludeDir = entry.getValue(); |
| } |
| else if (entry.getKey().equalsIgnoreCase(Constants.EXCLUDE_DIRECTIVE)) |
| { |
| m_activationExcludeDir = entry.getValue(); |
| } |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| // Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2, |
| // path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2 |
| public static void main(String[] headers) |
| { |
| String header = headers[0]; |
| |
| if (header != null) |
| { |
| if (header.length() == 0) |
| { |
| throw new IllegalArgumentException( |
| "A header cannot be an empty string."); |
| } |
| |
| List<ParsedHeaderClause> clauses = parseStandardHeader(header); |
| for (ParsedHeaderClause clause : clauses) |
| { |
| System.out.println("PATHS " + clause.m_paths); |
| System.out.println(" DIRS " + clause.m_dirs); |
| System.out.println(" ATTRS " + clause.m_attrs); |
| System.out.println(" TYPES " + clause.m_types); |
| } |
| } |
| |
| // return clauses; |
| } |
| |
| private static List<ParsedHeaderClause> parseStandardHeader(String header) |
| { |
| List<ParsedHeaderClause> clauses = new ArrayList<ParsedHeaderClause>(); |
| if (header != null) |
| { |
| int[] startIdx = new int[1]; |
| startIdx[0] = 0; |
| for (int i = 0; i < header.length(); i++) |
| { |
| clauses.add(parseClause(startIdx, header)); |
| i = startIdx[0]; |
| } |
| } |
| return clauses; |
| } |
| |
| private static ParsedHeaderClause parseClause(int[] startIdx, String header) |
| { |
| ParsedHeaderClause clause = new ParsedHeaderClause( |
| new ArrayList<String>(), |
| new HashMap<String, String>(), |
| new HashMap<String, Object>(), |
| new HashMap<String, String>()); |
| for (int i = startIdx[0]; i < header.length(); i++) |
| { |
| char c = header.charAt(i); |
| if ((c == ':') || (c == '=')) |
| { |
| parseClauseParameters(startIdx, header, clause); |
| i = startIdx[0]; |
| break; |
| } |
| else if ((c == ';') || (c == ',') || (i == (header.length() - 1))) |
| { |
| String path; |
| if (i == (header.length() - 1)) |
| { |
| path = header.substring(startIdx[0], header.length()); |
| } |
| else |
| { |
| path = header.substring(startIdx[0], i); |
| } |
| clause.m_paths.add(path.trim()); |
| startIdx[0] = i + 1; |
| if (c == ',') |
| { |
| break; |
| } |
| } |
| } |
| return clause; |
| } |
| |
| private static void parseClauseParameters( |
| int[] startIdx, String header, ParsedHeaderClause clause) |
| { |
| for (int i = startIdx[0]; i < header.length(); i++) |
| { |
| char c = header.charAt(i); |
| if ((c == ':') && (header.charAt(i + 1) == '=')) |
| { |
| parseClauseDirective(startIdx, header, clause); |
| i = startIdx[0]; |
| } |
| else if ((c == ':') || (c == '=')) |
| { |
| parseClauseAttribute(startIdx, header, clause); |
| i = startIdx[0]; |
| } |
| else if (c == ',') |
| { |
| startIdx[0] = i + 1; |
| break; |
| } |
| } |
| } |
| |
| private static void parseClauseDirective( |
| int[] startIdx, String header, ParsedHeaderClause clause) |
| { |
| String name = null; |
| String value = null; |
| boolean isQuoted = false; |
| boolean isEscaped = false; |
| for (int i = startIdx[0]; i < header.length(); i++) |
| { |
| char c = header.charAt(i); |
| if (!isEscaped && (c == '"')) |
| { |
| isQuoted = !isQuoted; |
| } |
| |
| if (!isEscaped |
| && !isQuoted && (c == ':')) |
| { |
| name = header.substring(startIdx[0], i); |
| startIdx[0] = i + 2; |
| } |
| else if (!isEscaped |
| && !isQuoted && ((c == ';') || (c == ',') || (i == (header.length() - 1)))) |
| { |
| if (i == (header.length() - 1)) |
| { |
| value = header.substring(startIdx[0], header.length()); |
| } |
| else |
| { |
| value = header.substring(startIdx[0], i); |
| } |
| if (c == ',') |
| { |
| startIdx[0] = i - 1; |
| } |
| else |
| { |
| startIdx[0] = i + 1; |
| } |
| break; |
| } |
| |
| isEscaped = (c == '\\'); |
| } |
| |
| // Trim whitespace. |
| name = name.trim(); |
| value = value.trim(); |
| |
| // Remove quotes, if value is quoted. |
| if (value.startsWith("\"") && value.endsWith("\"")) |
| { |
| value = value.substring(1, value.length() - 1); |
| } |
| |
| // Check for dupes. |
| if (clause.m_dirs.get(name) != null) |
| { |
| throw new IllegalArgumentException( |
| "Duplicate directive '" + name + "' in: " + header); |
| } |
| |
| clause.m_dirs.put(name, value); |
| } |
| |
| private static void parseClauseAttribute( |
| int[] startIdx, String header, ParsedHeaderClause clause) |
| { |
| String type = null; |
| |
| String name = parseClauseAttributeName(startIdx, header); |
| char c = header.charAt(startIdx[0]); |
| startIdx[0]++; |
| if (c == ':') |
| { |
| type = parseClauseAttributeType(startIdx, header); |
| } |
| |
| String value = parseClauseAttributeValue(startIdx, header); |
| |
| // Trim whitespace. |
| name = name.trim(); |
| value = value.trim(); |
| if (type != null) |
| { |
| type = type.trim(); |
| } |
| |
| // Remove quotes, if value is quoted. |
| if (value.startsWith("\"") && value.endsWith("\"")) |
| { |
| value = value.substring(1, value.length() - 1); |
| } |
| |
| // Check for dupes. |
| if (clause.m_attrs.get(name) != null) |
| { |
| throw new IllegalArgumentException( |
| "Duplicate attribute '" + name + "' in: " + header); |
| } |
| |
| clause.m_attrs.put(name, value); |
| if (type != null) |
| { |
| clause.m_types.put(name, type); |
| } |
| } |
| |
| private static String parseClauseAttributeName(int[] startIdx, String header) |
| { |
| for (int i = startIdx[0]; i < header.length(); i++) |
| { |
| char c = header.charAt(i); |
| if ((c == '=') || (c == ':')) |
| { |
| String name = header.substring(startIdx[0], i); |
| startIdx[0] = i; |
| return name; |
| } |
| } |
| return null; |
| } |
| |
| private static String parseClauseAttributeType(int[] startIdx, String header) |
| { |
| for (int i = startIdx[0]; i < header.length(); i++) |
| { |
| char c = header.charAt(i); |
| if (c == '=') |
| { |
| String type = header.substring(startIdx[0], i); |
| startIdx[0] = i + 1; |
| return type; |
| } |
| } |
| return null; |
| } |
| |
| private static String parseClauseAttributeValue(int[] startIdx, String header) |
| { |
| boolean isQuoted = false; |
| boolean isEscaped = false; |
| for (int i = startIdx[0]; i < header.length(); i++) |
| { |
| char c = header.charAt(i); |
| if (!isEscaped && (c == '"')) |
| { |
| isQuoted = !isQuoted; |
| } |
| |
| if (!isEscaped && |
| !isQuoted && ((c == ';') || (c == ',') || (i == (header.length() - 1)))) |
| { |
| String value; |
| if (i == (header.length() - 1)) |
| { |
| value = header.substring(startIdx[0], header.length()); |
| } |
| else |
| { |
| value = header.substring(startIdx[0], i); |
| } |
| if (c == ',') |
| { |
| startIdx[0] = i - 1; |
| } |
| else |
| { |
| startIdx[0] = i + 1; |
| } |
| return value; |
| } |
| |
| isEscaped = (c == '\\'); |
| } |
| return null; |
| } |
| |
| public static List<String> parseDelimitedString(String value, String delim) |
| { |
| return parseDelimitedString(value, delim, true); |
| } |
| |
| /** |
| * Parses delimited string and returns an array containing the tokens. This |
| * parser obeys quotes, so the delimiter character will be ignored if it is |
| * inside of a quote. This method assumes that the quote character is not |
| * included in the set of delimiter characters. |
| * @param value the delimited string to parse. |
| * @param delim the characters delimiting the tokens. |
| * @return a list of string or an empty list if there are none. |
| **/ |
| public static List<String> parseDelimitedString(String value, String delim, boolean trim) |
| { |
| if (value == null) |
| { |
| value = ""; |
| } |
| |
| List<String> list = new ArrayList(); |
| |
| int CHAR = 1; |
| int DELIMITER = 2; |
| int STARTQUOTE = 4; |
| int ENDQUOTE = 8; |
| |
| StringBuffer sb = new StringBuffer(); |
| |
| int expecting = (CHAR | DELIMITER | STARTQUOTE); |
| |
| boolean isEscaped = false; |
| for (int i = 0; i < value.length(); i++) |
| { |
| char c = value.charAt(i); |
| |
| boolean isDelimiter = (delim.indexOf(c) >= 0); |
| |
| if (c == '\\') |
| { |
| isEscaped = true; |
| continue; |
| } |
| |
| if (isEscaped) |
| { |
| sb.append(c); |
| } |
| else if (isDelimiter && ((expecting & DELIMITER) > 0)) |
| { |
| if (trim) |
| { |
| list.add(sb.toString().trim()); |
| } |
| else |
| { |
| list.add(sb.toString()); |
| } |
| sb.delete(0, sb.length()); |
| expecting = (CHAR | DELIMITER | STARTQUOTE); |
| } |
| else if ((c == '"') && ((expecting & STARTQUOTE) > 0)) |
| { |
| sb.append(c); |
| expecting = CHAR | ENDQUOTE; |
| } |
| else if ((c == '"') && ((expecting & ENDQUOTE) > 0)) |
| { |
| sb.append(c); |
| expecting = (CHAR | STARTQUOTE | DELIMITER); |
| } |
| else if ((expecting & CHAR) > 0) |
| { |
| sb.append(c); |
| } |
| else |
| { |
| throw new IllegalArgumentException("Invalid delimited string: " + value); |
| } |
| |
| isEscaped = false; |
| } |
| |
| if (sb.length() > 0) |
| { |
| if (trim) |
| { |
| list.add(sb.toString().trim()); |
| } |
| else |
| { |
| list.add(sb.toString()); |
| } |
| } |
| |
| return list; |
| } |
| |
| /** |
| * Parses native code manifest headers. |
| * @param libStrs an array of native library manifest header |
| * strings from the bundle manifest. |
| * @return an array of <tt>LibraryInfo</tt> objects for the |
| * passed in strings. |
| **/ |
| private static List<R4LibraryClause> parseLibraryStrings( |
| Logger logger, List<String> libStrs) |
| throws IllegalArgumentException |
| { |
| if (libStrs == null) |
| { |
| return new ArrayList<R4LibraryClause>(0); |
| } |
| |
| List<R4LibraryClause> libList = new ArrayList(libStrs.size()); |
| |
| for (int i = 0; i < libStrs.size(); i++) |
| { |
| R4LibraryClause clause = R4LibraryClause.parse(logger, libStrs.get(i)); |
| libList.add(clause); |
| } |
| |
| return libList; |
| } |
| } |