blob: f0e2bd7e9fffe0ca9e48c8356e983842b7c2b12f [file] [log] [blame]
/*
* 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 org.apache.felix.framework.Logger;
import org.apache.felix.framework.util.FelixConstants;
import org.apache.felix.framework.util.VersionRange;
import org.apache.felix.moduleloader.ICapability;
import org.apache.felix.moduleloader.IModule;
import org.apache.felix.moduleloader.IRequirement;
import org.osgi.framework.*;
public class ManifestParser
{
private final Logger m_logger;
private final Map m_configMap;
private final Map m_headerMap;
private volatile int m_activationPolicy = IModule.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 ICapability[] m_capabilities;
private volatile IRequirement[] m_requirements;
private volatile IRequirement[] m_dynamicRequirements;
private volatile R4LibraryClause[] m_libraryHeaders;
private volatile boolean m_libraryHeadersOptional = false;
public ManifestParser(Logger logger, Map configMap, Map headerMap)
throws BundleException
{
m_logger = logger;
m_configMap = configMap;
m_headerMap = headerMap;
// Verify that only manifest version 2 is specified.
String manifestVersion = (String) m_headerMap.get(Constants.BUNDLE_MANIFESTVERSION);
manifestVersion = (manifestVersion == null) ? null : manifestVersion.trim();
if ((manifestVersion != null) && !manifestVersion.equals("2"))
{
throw new BundleException(
"Unknown 'Bundle-ManifestVersion' value: " + manifestVersion);
}
// Create map to check for duplicate imports/exports
// and lists to hold capabilities and requirements.
List capList = new ArrayList();
List reqList = new ArrayList();
Map dupeMap = new HashMap();
//
// 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.
//
ICapability moduleCap = parseBundleSymbolicName(m_headerMap);
if (moduleCap != null)
{
m_bundleSymbolicName = (String)
moduleCap.getProperties().get(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE);
// Add a module capability and a host capability to all
// non-fragment bundles. A host capability is the same
// as a module capability, but with a different capability
// namespace. Module capabilities resolve required-bundle
// dependencies, while host capabilities resolve fragment-host
// dependencies.
if (headerMap.get(Constants.FRAGMENT_HOST) == null)
{
capList.add(moduleCap);
capList.add(new Capability(
ICapability.HOST_NAMESPACE, null,
((Capability) moduleCap).getAttributes()));
}
}
//
// Parse Fragment-Host.
//
Object[][][] clauses = parseStandardHeader(
(String) headerMap.get(Constants.FRAGMENT_HOST));
if (clauses.length > 0)
{
try
{
reqList.add(
new Requirement(
ICapability.HOST_NAMESPACE,
"(" + Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE
+ "=" + clauses[0][CLAUSE_PATHS_INDEX][0] + ")"));
}
catch (InvalidSyntaxException ex)
{
ex.printStackTrace();
}
}
//
// Parse Export-Package.
//
// Get exported packages from bundle manifest.
ICapability[] exportCaps = parseExportHeader(
(String) headerMap.get(Constants.EXPORT_PACKAGE));
// Verify that "java.*" packages are not exported.
for (int capIdx = 0; capIdx < exportCaps.length; capIdx++)
{
// Verify that the named package has not already been declared.
String pkgName = (String)
exportCaps[capIdx].getProperties().get(ICapability.PACKAGE_PROPERTY);
// Verify that java.* packages are not exported.
if (pkgName.startsWith("java."))
{
throw new BundleException(
"Exporting java.* packages not allowed: " + pkgName);
}
capList.add(exportCaps[capIdx]);
}
// Create an array of all capabilities.
m_capabilities = (ICapability[]) capList.toArray(new ICapability[capList.size()]);
//
// Parse Require-Bundle
//
IRequirement[] bundleReq = parseRequireBundleHeader(
(String) headerMap.get(Constants.REQUIRE_BUNDLE));
for (int reqIdx = 0; reqIdx < bundleReq.length; reqIdx++)
{
reqList.add(bundleReq[reqIdx]);
}
//
// Parse Import-Package.
//
// Get import packages from bundle manifest.
IRequirement[] importReqs = parseImportHeader(
(String) headerMap.get(Constants.IMPORT_PACKAGE));
// Create non-duplicated import array.
dupeMap.clear();
for (int reqIdx = 0; reqIdx < importReqs.length; reqIdx++)
{
// Verify that the named package has not already been declared.
String pkgName = ((Requirement) importReqs[reqIdx]).getTargetName();
if (dupeMap.get(pkgName) == null)
{
// Verify that java.* packages are not imported.
if (pkgName.startsWith("java."))
{
throw new BundleException(
"Importing java.* packages not allowed: " + pkgName);
}
dupeMap.put(pkgName, importReqs[reqIdx]);
}
else
{
throw new BundleException(
"Duplicate import - " + pkgName);
}
}
// Add import package requirements to requirement list.
reqList.addAll(dupeMap.values());
// Create an array of all requirements.
m_requirements = (IRequirement[]) reqList.toArray(new IRequirement[reqList.size()]);
//
// Parse DynamicImport-Package.
//
// Get dynamic import packages from bundle manifest.
m_dynamicRequirements = parseImportHeader(
(String) headerMap.get(Constants.DYNAMICIMPORT_PACKAGE));
// Dynamic imports can have duplicates, so just check for import
// of java.*.
for (int reqIdx = 0; reqIdx < m_dynamicRequirements.length; reqIdx++)
{
// Verify that java.* packages are not imported.
String pkgName = ((Requirement) m_dynamicRequirements[reqIdx]).getTargetName();
if (pkgName.startsWith("java."))
{
throw new BundleException(
"Dynamically importing java.* packages not allowed: " + pkgName);
}
}
//
// Parse Bundle-NativeCode.
//
// Get native library entry names for module library sources.
m_libraryHeaders =
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_libraryHeaders.length > 0) &&
(m_libraryHeaders[m_libraryHeaders.length - 1].getLibraryEntries() == null))
{
m_libraryHeadersOptional = true;
R4LibraryClause[] tmp = new R4LibraryClause[m_libraryHeaders.length - 1];
System.arraycopy(m_libraryHeaders, 0, tmp, 0, m_libraryHeaders.length - 1);
m_libraryHeaders = tmp;
}
//
// Parse activation policy.
//
// This sets m_activationPolicy, m_includedPolicyClasses, and
// m_excludedPolicyClasses.
parseActivationPolicy(headerMap);
// Do final checks and normalization of manifest.
if (getManifestVersion().equals("2"))
{
checkAndNormalizeR4();
}
else
{
checkAndNormalizeR3();
}
}
public String getManifestVersion()
{
String manifestVersion = (String) m_headerMap.get(Constants.BUNDLE_MANIFESTVERSION);
return (manifestVersion == null) ? "1" : 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 ICapability[] getCapabilities()
{
return m_capabilities;
}
public IRequirement[] getRequirements()
{
return m_requirements;
}
public IRequirement[] getDynamicRequirements()
{
return m_dynamicRequirements;
}
public R4LibraryClause[] getLibraryClauses()
{
return m_libraryHeaders;
}
/**
* <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 R4Library[] getLibraries()
{
R4Library[] libs = null;
try
{
R4LibraryClause clause = getSelectedLibraryClause();
if (clause != null)
{
String[] entries = clause.getLibraryEntries();
libs = new R4Library[entries.length];
int current = 0;
for (int i = 0; i < libs.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[current++] = new R4Library(
clause.getLibraryEntries()[i],
clause.getOSNames(), clause.getProcessors(), clause.getOSVersions(),
clause.getLanguages(), clause.getSelectionFilter());
}
}
if (current < libs.length)
{
R4Library[] tmp = new R4Library[current];
System.arraycopy(libs, 0, tmp, 0, current);
libs = tmp;
}
}
}
catch (Exception ex)
{
libs = new 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_libraryHeaders != null) && (m_libraryHeaders.length > 0))
{
List clauseList = new ArrayList();
// Search for matching native clauses.
for (int i = 0; i < m_libraryHeaders.length; i++)
{
if (m_libraryHeaders[i].match(m_configMap))
{
clauseList.add(m_libraryHeaders[i]);
}
}
// Select the matching native clause.
int selected = 0;
if (clauseList.size() == 0)
{
// 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 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.getLow()).compareTo(osVersionRangeMaxFloor) >= 0)
{
osVersionRangeMaxFloor = range.getLow();
}
}
}
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.getLow()).compareTo(osVersionRangeMaxFloor) >= 0)
{
selection.add("" + indexList.get(i));
}
}
}
}
if (selection.size() == 0)
{
// 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.size() == 0)
{
return 0;
}
else
{
return Integer.parseInt(selection.get(0).toString());
}
}
private void checkAndNormalizeR3() throws BundleException
{
// Check to make sure that R3 bundles have only specified
// the 'specification-version' attribute and no directives
// on their exports; ignore all unknown attributes.
for (int capIdx = 0;
(m_capabilities != null) && (capIdx < m_capabilities.length);
capIdx++)
{
if (m_capabilities[capIdx].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
{
// R3 bundles cannot have directives on their exports.
if (((Capability) m_capabilities[capIdx]).getDirectives().length != 0)
{
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 (((Capability) m_capabilities[capIdx]).getAttributes() != null)
{
// R3 package capabilities should only have name and
// version attributes.
R4Attribute pkgName = null;
R4Attribute pkgVersion = new R4Attribute(ICapability.VERSION_PROPERTY, Version.emptyVersion, false);
for (int attrIdx = 0;
attrIdx < ((Capability) m_capabilities[capIdx]).getAttributes().length;
attrIdx++)
{
if (((Capability) m_capabilities[capIdx]).getAttributes()[attrIdx]
.getName().equals(ICapability.PACKAGE_PROPERTY))
{
pkgName = ((Capability) m_capabilities[capIdx]).getAttributes()[attrIdx];
}
else if (((Capability) m_capabilities[capIdx]).getAttributes()[attrIdx]
.getName().equals(ICapability.VERSION_PROPERTY))
{
pkgVersion = ((Capability) m_capabilities[capIdx]).getAttributes()[attrIdx];
}
else
{
m_logger.log(Logger.LOG_WARNING,
"Unknown R3 export attribute: "
+ ((Capability) m_capabilities[capIdx]).getAttributes()[attrIdx].getName());
}
}
// Recreate the export to remove any other attributes
// and add version if missing.
m_capabilities[capIdx] = new Capability(
ICapability.PACKAGE_NAMESPACE,
null,
new R4Attribute[] { pkgName, pkgVersion } );
}
}
}
// Check to make sure that R3 bundles have only specified
// the 'specification-version' attribute and no directives
// on their imports; ignore all unknown attributes.
for (int reqIdx = 0; (m_requirements != null) && (reqIdx < m_requirements.length); reqIdx++)
{
if (m_requirements[reqIdx].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
{
// R3 bundles cannot have directives on their imports.
if (((Requirement) m_requirements[reqIdx]).getDirectives().length != 0)
{
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 (((Requirement) m_requirements[reqIdx]).getAttributes() != null)
{
// R3 package requirements should only have name and
// version attributes.
R4Attribute pkgName = null;
R4Attribute pkgVersion =
new R4Attribute(ICapability.VERSION_PROPERTY,
new VersionRange(Version.emptyVersion, true, null, true), false);
for (int attrIdx = 0;
attrIdx < ((Requirement) m_requirements[reqIdx]).getAttributes().length;
attrIdx++)
{
if (((Requirement) m_requirements[reqIdx]).getAttributes()[attrIdx]
.getName().equals(ICapability.PACKAGE_PROPERTY))
{
pkgName = ((Requirement) m_requirements[reqIdx]).getAttributes()[attrIdx];
}
else if (((Requirement) m_requirements[reqIdx]).getAttributes()[attrIdx]
.getName().equals(ICapability.VERSION_PROPERTY))
{
pkgVersion = ((Requirement) m_requirements[reqIdx]).getAttributes()[attrIdx];
}
else
{
m_logger.log(Logger.LOG_WARNING,
"Unknown R3 import attribute: "
+ ((Requirement) m_requirements[reqIdx]).getAttributes()[attrIdx].getName());
}
}
// Recreate the import to remove any other attributes
// and add version if missing.
m_requirements[reqIdx] = new Requirement(
ICapability.PACKAGE_NAMESPACE,
null,
new R4Attribute[] { pkgName, pkgVersion });
}
}
}
// 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 i = 0; i < m_requirements.length; i++)
{
if (m_requirements[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
{
map.put(
((Requirement) m_requirements[i]).getTargetName(),
m_requirements[i]);
}
}
// Add import requirement for each export capability.
for (int i = 0; i < m_capabilities.length; i++)
{
if (m_capabilities[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE) &&
(map.get(m_capabilities[i].getProperties().get(ICapability.PACKAGE_PROPERTY)) == null))
{
// Convert Version to VersionRange.
R4Attribute[] attrs = (R4Attribute[]) ((Capability) m_capabilities[i]).getAttributes().clone();
for (int attrIdx = 0; (attrs != null) && (attrIdx < attrs.length); attrIdx++)
{
if (attrs[attrIdx].getName().equals(Constants.VERSION_ATTRIBUTE))
{
attrs[attrIdx] = new R4Attribute(
attrs[attrIdx].getName(),
VersionRange.parse(attrs[attrIdx].getValue().toString()),
attrs[attrIdx].isMandatory());
}
}
map.put(
m_capabilities[i].getProperties().get(ICapability.PACKAGE_PROPERTY),
new Requirement(ICapability.PACKAGE_NAMESPACE, null, attrs));
}
}
m_requirements =
(IRequirement[]) map.values().toArray(new IRequirement[map.size()]);
// 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; (m_requirements != null) && (i < m_requirements.length); i++)
{
if (m_requirements[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
{
usesValue = usesValue
+ ((usesValue.length() > 0) ? "," : "")
+ ((Requirement) m_requirements[i]).getTargetName();
}
}
R4Directive uses = new R4Directive(
Constants.USES_DIRECTIVE, usesValue);
for (int i = 0; (m_capabilities != null) && (i < m_capabilities.length); i++)
{
if (m_capabilities[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
{
m_capabilities[i] = new Capability(
ICapability.PACKAGE_NAMESPACE,
new R4Directive[] { uses },
((Capability) m_capabilities[i]).getAttributes());
}
}
// Check to make sure that R3 bundles have no attributes or
// directives on their dynamic imports.
for (int i = 0;
(m_dynamicRequirements != null) && (i < m_dynamicRequirements.length);
i++)
{
if (((Requirement) m_dynamicRequirements[i]).getDirectives().length != 0)
{
throw new BundleException("R3 dynamic imports cannot contain directives.");
}
if (((Requirement) m_dynamicRequirements[i]).getAttributes().length != 0)
{
// throw new BundleException("R3 dynamic imports cannot contain attributes.");
}
}
}
private void checkAndNormalizeR4() throws BundleException
{
// Verify that bundle symbolic name is specified.
if (m_bundleSymbolicName == null)
{
throw new BundleException("R4 bundle manifests must include bundle symbolic name.");
}
m_capabilities = checkAndNormalizeR4Exports(
m_capabilities, m_bundleSymbolicName, m_bundleVersion);
R4Directive extension = parseExtensionBundleHeader((String)
m_headerMap.get(Constants.FRAGMENT_HOST));
if (extension != null)
{
if (!(Constants.EXTENSION_FRAMEWORK.equals(extension.getValue()) ||
Constants.EXTENSION_BOOTCLASSPATH.equals(extension.getValue())))
{
throw new BundleException(
"Extension bundle must have either 'extension:=framework' or 'extension:=bootclasspath'");
}
checkExtensionBundle();
m_isExtension = true;
}
}
private static ICapability[] checkAndNormalizeR4Exports(
ICapability[] caps, String bsn, Version bv)
throws BundleException
{
// Verify that the exports do not specify bundle symbolic name
// or bundle version.
for (int i = 0; (caps != null) && (i < caps.length); i++)
{
if (caps[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
{
R4Attribute[] attrs = ((Capability) caps[i]).getAttributes();
for (int attrIdx = 0; attrIdx < attrs.length; attrIdx++)
{
// Find symbolic name and version attribute, if present.
if (attrs[attrIdx].getName().equals(Constants.BUNDLE_VERSION_ATTRIBUTE) ||
attrs[attrIdx].getName().equals(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.
R4Attribute[] newAttrs = new R4Attribute[attrs.length + 2];
System.arraycopy(attrs, 0, newAttrs, 0, attrs.length);
newAttrs[attrs.length] = new R4Attribute(
Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, bsn, false);
newAttrs[attrs.length + 1] = new R4Attribute(
Constants.BUNDLE_VERSION_ATTRIBUTE, bv, false);
caps[i] = new Capability(
ICapability.PACKAGE_NAMESPACE,
((Capability) caps[i]).getDirectives(),
newAttrs);
}
}
return caps;
}
private void checkExtensionBundle() throws BundleException
{
if (m_headerMap.containsKey(Constants.IMPORT_PACKAGE) ||
m_headerMap.containsKey(Constants.REQUIRE_BUNDLE) ||
m_headerMap.containsKey(Constants.BUNDLE_NATIVECODE) ||
m_headerMap.containsKey(Constants.DYNAMICIMPORT_PACKAGE) ||
m_headerMap.containsKey(Constants.BUNDLE_ACTIVATOR))
{
throw new BundleException("Invalid extension bundle manifest");
}
}
private static ICapability parseBundleSymbolicName(Map headerMap)
throws BundleException
{
Object[][][] clauses = parseStandardHeader(
(String) headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
if (clauses.length > 0)
{
if (clauses.length > 1)
{
throw new BundleException(
"Cannot have multiple symbolic names: "
+ headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
}
else if (clauses[0][CLAUSE_PATHS_INDEX].length > 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.
if (((String) headerMap.get(Constants.BUNDLE_MANIFESTVERSION)).equals("2"))
{
throw ex;
}
bundleVersion = Version.emptyVersion;
}
}
// Create a module capability and return it.
String symName = (String) clauses[0][CLAUSE_PATHS_INDEX][0];
R4Attribute[] attrs = new R4Attribute[2];
attrs[0] = new R4Attribute(
Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, symName, false);
attrs[1] = new R4Attribute(
Constants.BUNDLE_VERSION_ATTRIBUTE, bundleVersion, false);
return new Capability(ICapability.MODULE_NAMESPACE, (R4Directive[]) clauses[0][CLAUSE_DIRECTIVES_INDEX], attrs);
}
return null;
}
public static ICapability[] parseExportHeader(String header, String bsn, Version bv)
throws BundleException
{
ICapability[] caps = parseExportHeader(header);
try
{
caps = checkAndNormalizeR4Exports(caps, bsn, bv);
}
catch (BundleException ex)
{
caps = null;
}
return caps;
}
private static ICapability[] parseExportHeader(String header)
{
Object[][][] clauses = parseStandardHeader(header);
// TODO: FRAMEWORK - Perhaps verification/normalization should be completely
// separated from parsing, since verification/normalization may vary.
// If both version and specification-version attributes are specified,
// then verify that the values are equal.
Map attrMap = new HashMap();
for (int clauseIdx = 0; clauseIdx < clauses.length; clauseIdx++)
{
// Put attributes for current clause in a map for easy lookup.
attrMap.clear();
for (int attrIdx = 0;
attrIdx < clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX].length;
attrIdx++)
{
R4Attribute attr = (R4Attribute) clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX][attrIdx];
attrMap.put(attr.getName(), attr);
}
// Check for "version" and "specification-version" attributes
// and verify they are the same if both are specified.
R4Attribute v = (R4Attribute) attrMap.get(Constants.VERSION_ATTRIBUTE);
R4Attribute sv = (R4Attribute) attrMap.get(Constants.PACKAGE_SPECIFICATION_VERSION);
if ((v != null) && (sv != null))
{
// Verify they are equal.
if (!((String) v.getValue()).trim().equals(((String) sv.getValue()).trim()))
{
throw new IllegalArgumentException(
"Both version and specificat-version are specified, but they are not equal.");
}
}
// Always add the default version if not specified.
if ((v == null) && (sv == null))
{
v = new R4Attribute(
Constants.VERSION_ATTRIBUTE, Version.emptyVersion, false);
}
// 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.
attrMap.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
v = (v == null) ? sv : v;
attrMap.put(Constants.VERSION_ATTRIBUTE,
new R4Attribute(
Constants.VERSION_ATTRIBUTE,
Version.parseVersion(v.getValue().toString()),
v.isMandatory()));
// Re-copy the attribute array since it has changed.
clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX] =
attrMap.values().toArray(new R4Attribute[attrMap.size()]);
}
}
// Now convert generic header clauses into capabilities.
List capList = new ArrayList();
for (int clauseIdx = 0; clauseIdx < clauses.length; clauseIdx++)
{
for (int pathIdx = 0;
pathIdx < clauses[clauseIdx][CLAUSE_PATHS_INDEX].length;
pathIdx++)
{
// Make sure a package name was specified.
if (((String) clauses[clauseIdx][CLAUSE_PATHS_INDEX][pathIdx]).length() == 0)
{
throw new IllegalArgumentException(
"An empty package name was specified: " + header);
}
// Prepend the package name to the array of attributes.
R4Attribute[] attrs = (R4Attribute[]) clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX];
R4Attribute[] newAttrs = new R4Attribute[attrs.length + 1];
newAttrs[0] = new R4Attribute(
ICapability.PACKAGE_PROPERTY,
clauses[clauseIdx][CLAUSE_PATHS_INDEX][pathIdx], false);
System.arraycopy(attrs, 0, newAttrs, 1, attrs.length);
// Create package capability and add to capability list.
capList.add(
new Capability(
ICapability.PACKAGE_NAMESPACE,
(R4Directive[]) clauses[clauseIdx][CLAUSE_DIRECTIVES_INDEX],
newAttrs));
}
}
return (ICapability[]) capList.toArray(new ICapability[capList.size()]);
}
private static IRequirement[] parseImportHeader(String header)
{
Object[][][] clauses = parseStandardHeader(header);
// TODO: FRAMEWORK - Perhaps verification/normalization should be completely
// separated from parsing, since verification/normalization may vary.
// Verify that the values are equals if the package specifies
// both version and specification-version attributes.
Map attrMap = new HashMap();
for (int clauseIdx = 0; clauseIdx < clauses.length; clauseIdx++)
{
// Put attributes for current clause in a map for easy lookup.
attrMap.clear();
for (int attrIdx = 0;
attrIdx < clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX].length;
attrIdx++)
{
R4Attribute attr = (R4Attribute) clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX][attrIdx];
attrMap.put(attr.getName(), attr);
}
// Check for "version" and "specification-version" attributes
// and verify they are the same if both are specified.
R4Attribute v = (R4Attribute) attrMap.get(Constants.VERSION_ATTRIBUTE);
R4Attribute sv = (R4Attribute) attrMap.get(Constants.PACKAGE_SPECIFICATION_VERSION);
if ((v != null) && (sv != null))
{
// Verify they are equal.
if (!((String) v.getValue()).trim().equals(((String) sv.getValue()).trim()))
{
throw new IllegalArgumentException(
"Both version and specificat-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))
{
attrMap.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
v = (v == null) ? sv : v;
attrMap.put(Constants.VERSION_ATTRIBUTE,
new R4Attribute(
Constants.VERSION_ATTRIBUTE,
VersionRange.parse(v.getValue().toString()),
v.isMandatory()));
}
// If bundle version is specified, then convert its type to VersionRange.
v = (R4Attribute) attrMap.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
if (v != null)
{
attrMap.put(Constants.BUNDLE_VERSION_ATTRIBUTE,
new R4Attribute(
Constants.BUNDLE_VERSION_ATTRIBUTE,
VersionRange.parse(v.getValue().toString()),
v.isMandatory()));
}
// Re-copy the attribute array in case it has changed.
clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX] =
attrMap.values().toArray(new R4Attribute[attrMap.size()]);
}
// Now convert generic header clauses into requirements.
List reqList = new ArrayList();
for (int clauseIdx = 0; clauseIdx < clauses.length; clauseIdx++)
{
for (int pathIdx = 0;
pathIdx < clauses[clauseIdx][CLAUSE_PATHS_INDEX].length;
pathIdx++)
{
// Make sure a package name was specified.
if (((String) clauses[clauseIdx][CLAUSE_PATHS_INDEX][pathIdx]).length() == 0)
{
throw new IllegalArgumentException(
"An empty package name was specified: " + header);
}
// Prepend the package name to the array of attributes.
R4Attribute[] attrs = (R4Attribute[]) clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX];
R4Attribute[] newAttrs = new R4Attribute[attrs.length + 1];
newAttrs[0] = new R4Attribute(
ICapability.PACKAGE_PROPERTY,
clauses[clauseIdx][CLAUSE_PATHS_INDEX][pathIdx], false);
System.arraycopy(attrs, 0, newAttrs, 1, attrs.length);
// Create package requirement and add to requirement list.
reqList.add(
new Requirement(
ICapability.PACKAGE_NAMESPACE,
(R4Directive[]) clauses[clauseIdx][CLAUSE_DIRECTIVES_INDEX],
newAttrs));
}
}
return (IRequirement[]) reqList.toArray(new IRequirement[reqList.size()]);
}
private static IRequirement[] parseRequireBundleHeader(String header)
{
Object[][][] clauses = parseStandardHeader(header);
// TODO: FRAMEWORK - Perhaps verification/normalization should be completely
// separated from parsing, since verification/normalization may vary.
// Convert bundle version attribute to VersionRange type.
for (int clauseIdx = 0; clauseIdx < clauses.length; clauseIdx++)
{
for (int attrIdx = 0;
attrIdx < clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX].length;
attrIdx++)
{
R4Attribute attr = (R4Attribute) clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX][attrIdx];
if (attr.getName().equals(Constants.BUNDLE_VERSION_ATTRIBUTE))
{
clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX][attrIdx] =
new R4Attribute(
Constants.BUNDLE_VERSION_ATTRIBUTE,
VersionRange.parse(attr.getValue().toString()),
attr.isMandatory());
}
}
}
// Now convert generic header clauses into requirements.
List reqList = new ArrayList();
for (int clauseIdx = 0; clauseIdx < clauses.length; clauseIdx++)
{
for (int pathIdx = 0;
pathIdx < clauses[clauseIdx][CLAUSE_PATHS_INDEX].length;
pathIdx++)
{
// Prepend the symbolic name to the array of attributes.
R4Attribute[] attrs = (R4Attribute[]) clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX];
R4Attribute[] newAttrs = new R4Attribute[attrs.length + 1];
newAttrs[0] = new R4Attribute(
Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE,
clauses[clauseIdx][CLAUSE_PATHS_INDEX][pathIdx], false);
System.arraycopy(attrs, 0, newAttrs, 1, attrs.length);
// Create package requirement and add to requirement list.
reqList.add(
new Requirement(
ICapability.MODULE_NAMESPACE,
(R4Directive[]) clauses[clauseIdx][CLAUSE_DIRECTIVES_INDEX],
newAttrs));
}
}
return (IRequirement[]) reqList.toArray(new IRequirement[reqList.size()]);
}
public static R4Directive parseExtensionBundleHeader(String header)
throws BundleException
{
Object[][][] clauses = parseStandardHeader(header);
R4Directive result = null;
if (clauses.length == 1)
{
// See if there is the "extension" directive.
for (int i = 0;
(result == null) && (i < clauses[0][CLAUSE_DIRECTIVES_INDEX].length);
i++)
{
if (Constants.EXTENSION_DIRECTIVE.equals(((R4Directive)
clauses[0][CLAUSE_DIRECTIVES_INDEX][i]).getName()))
{
// If the extension directive is specified, make sure
// the target is the system bundle.
if (FelixConstants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(clauses[0][CLAUSE_PATHS_INDEX][0]) ||
Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(clauses[0][CLAUSE_PATHS_INDEX][0]))
{
result = (R4Directive) clauses[0][CLAUSE_DIRECTIVES_INDEX][i];
}
else
{
throw new BundleException(
"Only the system bundle can have extension bundles.");
}
}
}
}
return result;
}
private void parseActivationPolicy(Map headerMap)
{
m_activationPolicy = IModule.EAGER_ACTIVATION;
Object[][][] clauses = parseStandardHeader(
(String) headerMap.get(Constants.BUNDLE_ACTIVATIONPOLICY));
if (clauses.length > 0)
{
// Just look for a "path" matching the lazy policy, ignore
// everything else.
for (int i = 0;
i < clauses[0][CLAUSE_PATHS_INDEX].length;
i++)
{
if (clauses[0][CLAUSE_PATHS_INDEX][i].equals(Constants.ACTIVATION_LAZY))
{
m_activationPolicy = IModule.LAZY_ACTIVATION;
for (int j = 0; j < clauses[0][CLAUSE_DIRECTIVES_INDEX].length; j++)
{
R4Directive dir = (R4Directive) clauses[0][CLAUSE_DIRECTIVES_INDEX][j];
if (dir.getName().equalsIgnoreCase(Constants.INCLUDE_DIRECTIVE))
{
m_activationIncludeDir = dir.getValue();
}
else if (dir.getName().equalsIgnoreCase(Constants.EXCLUDE_DIRECTIVE))
{
m_activationExcludeDir = dir.getValue();
}
}
break;
}
}
}
}
public static final int CLAUSE_PATHS_INDEX = 0;
public static final int CLAUSE_DIRECTIVES_INDEX = 1;
public static final int CLAUSE_ATTRIBUTES_INDEX = 2;
// Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2,
// path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
private static Object[][][] parseStandardHeader(String header)
{
Object[][][] clauses = null;
if (header != null)
{
if (header.length() == 0)
{
throw new IllegalArgumentException(
"A header cannot be an empty string.");
}
String[] clauseStrings = parseDelimitedString(
header, FelixConstants.CLASS_PATH_SEPARATOR);
List completeList = new ArrayList();
for (int i = 0; (clauseStrings != null) && (i < clauseStrings.length); i++)
{
completeList.add(parseStandardHeaderClause(clauseStrings[i]));
}
clauses = (Object[][][]) completeList.toArray(new Object[completeList.size()][][]);
}
return (clauses == null) ? new Object[0][][] : clauses;
}
// Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
private static Object[][] parseStandardHeaderClause(String clauseString)
throws IllegalArgumentException
{
// Break string into semi-colon delimited pieces.
String[] pieces = parseDelimitedString(
clauseString, FelixConstants.PACKAGE_SEPARATOR);
// Count the number of different paths; paths
// will not have an '=' in their string. This assumes
// that paths come first, before directives and
// attributes.
int pathCount = 0;
for (int pieceIdx = 0; pieceIdx < pieces.length; pieceIdx++)
{
if (pieces[pieceIdx].indexOf('=') >= 0)
{
break;
}
pathCount++;
}
// Error if no paths were specified.
if (pathCount == 0)
{
throw new IllegalArgumentException(
"No paths specified in header: " + clauseString);
}
// Create an array of paths.
String[] paths = new String[pathCount];
System.arraycopy(pieces, 0, paths, 0, pathCount);
// Parse the directives/attributes.
Map dirsMap = new HashMap();
Map attrsMap = new HashMap();
int idx = -1;
String sep = null;
for (int pieceIdx = pathCount; pieceIdx < pieces.length; pieceIdx++)
{
// Check if it is a directive.
if ((idx = pieces[pieceIdx].indexOf(FelixConstants.DIRECTIVE_SEPARATOR)) >= 0)
{
sep = FelixConstants.DIRECTIVE_SEPARATOR;
}
// Check if it is an attribute.
else if ((idx = pieces[pieceIdx].indexOf(FelixConstants.ATTRIBUTE_SEPARATOR)) >= 0)
{
sep = FelixConstants.ATTRIBUTE_SEPARATOR;
}
// It is an error.
else
{
throw new IllegalArgumentException("Not a directive/attribute: " + clauseString);
}
String key = pieces[pieceIdx].substring(0, idx).trim();
String value = pieces[pieceIdx].substring(idx + sep.length()).trim();
// Remove quotes, if value is quoted.
if (value.startsWith("\"") && value.endsWith("\""))
{
value = value.substring(1, value.length() - 1);
}
// Save the directive/attribute in the appropriate array.
if (sep.equals(FelixConstants.DIRECTIVE_SEPARATOR))
{
// Check for duplicates.
if (dirsMap.get(key) != null)
{
throw new IllegalArgumentException(
"Duplicate directive: " + key);
}
dirsMap.put(key, new R4Directive(key, value));
}
else
{
// Check for duplicates.
if (attrsMap.get(key) != null)
{
throw new IllegalArgumentException(
"Duplicate attribute: " + key);
}
attrsMap.put(key, new R4Attribute(key, value, false));
}
}
// Create directive array.
R4Directive[] dirs = (R4Directive[])
dirsMap.values().toArray(new R4Directive[dirsMap.size()]);
// Create attribute array.
R4Attribute[] attrs = (R4Attribute[])
attrsMap.values().toArray(new R4Attribute[attrsMap.size()]);
// Create an array to hold the parsed paths, directives, and attributes.
Object[][] clause = new Object[3][];
clause[CLAUSE_PATHS_INDEX] = paths;
clause[CLAUSE_DIRECTIVES_INDEX] = dirs;
clause[CLAUSE_ATTRIBUTES_INDEX] = attrs;
return clause;
}
/**
* 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 an array of string tokens or null if there were no tokens.
**/
public static String[] parseDelimitedString(String value, String delim)
{
if (value == null)
{
value = "";
}
List list = new ArrayList();
int CHAR = 1;
int DELIMITER = 2;
int STARTQUOTE = 4;
int ENDQUOTE = 8;
StringBuffer sb = new StringBuffer();
int expecting = (CHAR | DELIMITER | STARTQUOTE);
for (int i = 0; i < value.length(); i++)
{
char c = value.charAt(i);
boolean isDelimiter = (delim.indexOf(c) >= 0);
boolean isQuote = (c == '"');
if (isDelimiter && ((expecting & DELIMITER) > 0))
{
list.add(sb.toString().trim());
sb.delete(0, sb.length());
expecting = (CHAR | DELIMITER | STARTQUOTE);
}
else if (isQuote && ((expecting & STARTQUOTE) > 0))
{
sb.append(c);
expecting = CHAR | ENDQUOTE;
}
else if (isQuote && ((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);
}
}
if (sb.length() > 0)
{
list.add(sb.toString().trim());
}
return (String[]) list.toArray(new String[list.size()]);
}
/**
* 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 R4LibraryClause[] parseLibraryStrings(Logger logger, String[] libStrs)
throws IllegalArgumentException
{
if (libStrs == null)
{
return new R4LibraryClause[0];
}
List libList = new ArrayList();
for (int i = 0; i < libStrs.length; i++)
{
R4LibraryClause clause = R4LibraryClause.parse(logger, libStrs[i]);
libList.add(clause);
}
return (R4LibraryClause[]) libList.toArray(new R4LibraryClause[libList.size()]);
}
}