blob: 0d212771a8aa98c98f7b9dd9f28fd3089541f282 [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 java.util.ArrayList;
import java.util.Map.Entry;
import org.apache.felix.framework.Logger;
import org.apache.felix.framework.capabilityset.Capability;
import org.apache.felix.framework.capabilityset.Attribute;
import org.apache.felix.framework.capabilityset.Directive;
import org.apache.felix.framework.resolver.Module;
import org.apache.felix.framework.capabilityset.Requirement;
import org.apache.felix.framework.util.FelixConstants;
import org.apache.felix.framework.util.VersionRange;
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 = Module.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<Capability> m_capabilities;
private volatile List<Requirement> m_requirements;
private volatile List<Requirement> m_dynamicRequirements;
private volatile List<R4LibraryClause> m_libraryClauses;
private volatile boolean m_libraryHeadersOptional = false;
public ManifestParser(Logger logger, Map configMap, Module 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<Capability> 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.
//
Capability moduleCap = parseBundleSymbolicName(owner, m_headerMap);
if (moduleCap != null)
{
m_bundleSymbolicName = (String)
moduleCap.getAttribute(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE).getValue();
// 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 CapabilityImpl(
owner, Capability.HOST_NAMESPACE, new ArrayList<Directive>(0),
((CapabilityImpl) moduleCap).getAttributes()));
}
}
// 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<Requirement> hostReqs = parseFragmentHost(m_logger, m_headerMap);
//
// Parse Require-Bundle
//
List<ParsedHeaderClause> requireClauses =
parseStandardHeader((String) headerMap.get(Constants.REQUIRE_BUNDLE));
requireClauses = normalizeRequireClauses(m_logger, requireClauses, getManifestVersion());
List<Requirement> requireReqs = convertRequires(requireClauses);
//
// Parse Import-Package.
//
List<ParsedHeaderClause> importClauses =
parseStandardHeader((String) headerMap.get(Constants.IMPORT_PACKAGE));
importClauses = normalizeImportClauses(m_logger, importClauses, getManifestVersion());
List<Requirement> importReqs = convertImports(importClauses);
//
// Parse DynamicImport-Package.
//
List<ParsedHeaderClause> dynamicClauses =
parseStandardHeader((String) headerMap.get(Constants.DYNAMICIMPORT_PACKAGE));
dynamicClauses = normalizeDynamicImportClauses(m_logger, dynamicClauses, getManifestVersion());
m_dynamicRequirements = convertImports(dynamicClauses);
//
// Parse Export-Package.
//
// Get exported packages from bundle manifest.
List<ParsedHeaderClause> exportClauses =
parseStandardHeader((String) headerMap.get(Constants.EXPORT_PACKAGE));
exportClauses = normalizeExportClauses(logger, exportClauses,
getManifestVersion(), m_bundleSymbolicName, m_bundleVersion);
List<Capability> exportCaps = convertExports(exportClauses, owner);
//
// Calculate implicit imports.
//
if (!getManifestVersion().equals("2"))
{
List<ParsedHeaderClause> implicitClauses =
calculateImplicitImports(exportCaps, importClauses);
importReqs.addAll(convertImports(implicitClauses));
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());
m_capabilities.addAll(capList);
m_capabilities.addAll(exportCaps);
// Combine all requirements.
m_requirements = new ArrayList(
importReqs.size() + requireReqs.size() + hostReqs.size());
m_requirements.addAll(importReqs);
m_requirements.addAll(requireReqs);
m_requirements.addAll(hostReqs);
//
// Parse Bundle-NativeCode.
//
// Get native library entry names for module library sources.
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.size() > 0) &&
(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 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.
Map<String, Attribute> attrMap = new HashMap();
for (int clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
// Put attributes for current clause in a map for easy lookup.
attrMap.clear();
for (int attrIdx = 0;
attrIdx < clauses.get(clauseIdx).m_attrs.size();
attrIdx++)
{
Attribute attr = clauses.get(clauseIdx).m_attrs.get(attrIdx);
attrMap.put(attr.getName(), attr);
}
// Check for "version" and "specification-version" attributes
// and verify they are the same if both are specified.
Attribute v = attrMap.get(Constants.VERSION_ATTRIBUTE);
Attribute sv = 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 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))
{
attrMap.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
v = (v == null) ? sv : v;
attrMap.put(Constants.VERSION_ATTRIBUTE,
new Attribute(
Constants.VERSION_ATTRIBUTE,
VersionRange.parse(v.getValue().toString()),
v.isMandatory()));
}
// If bundle version is specified, then convert its type to VersionRange.
v = attrMap.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
if (v != null)
{
attrMap.put(Constants.BUNDLE_VERSION_ATTRIBUTE,
new Attribute(
Constants.BUNDLE_VERSION_ATTRIBUTE,
VersionRange.parse(v.getValue().toString()),
v.isMandatory()));
}
// Re-copy the attributes in case they changed.
clauses.get(clauseIdx).m_attrs.clear();
clauses.get(clauseIdx).m_attrs.addAll(attrMap.values());
}
// Verify java.* is not imported, nor any duplicate imports.
Set dupeSet = new HashSet();
for (int clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
// Verify that the named package has not already been declared.
List<String> paths = clauses.get(clauseIdx).m_paths;
for (int pathIdx = 0; pathIdx < paths.size(); pathIdx++)
{
String pkgName = paths.get(pathIdx);
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 (clauses.get(clauseIdx).m_paths.get(pathIdx).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"))
{
// 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 clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
// R3 bundles cannot have directives on their imports.
if (clauses.get(clauseIdx).m_dirs.size() != 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 (clauses.get(clauseIdx).m_attrs.size() != 0)
{
// R3 package requirements should only have version attributes.
Attribute pkgVersion =
new Attribute(Capability.VERSION_ATTR,
new VersionRange(Version.emptyVersion, true, null, true), false);
for (int attrIdx = 0;
attrIdx < clauses.get(clauseIdx).m_attrs.size();
attrIdx++)
{
if (clauses.get(clauseIdx).m_attrs.get(attrIdx)
.getName().equals(Capability.VERSION_ATTR))
{
pkgVersion = clauses.get(clauseIdx).m_attrs.get(attrIdx);
}
else
{
logger.log(Logger.LOG_WARNING,
"Unknown R3 import attribute: "
+ clauses.get(clauseIdx).m_attrs.get(attrIdx).getName());
}
}
// Recreate the import to remove any other attributes
// and add version if missing.
ArrayList<Attribute> attrs = new ArrayList<Attribute>(1);
attrs.add(pkgVersion);
clauses.set(clauseIdx, new ParsedHeaderClause(
clauses.get(clauseIdx).m_paths,
clauses.get(clauseIdx).m_dirs,
attrs));
}
}
}
return clauses;
}
private static List<Requirement> convertImports(List<ParsedHeaderClause> clauses)
{
// Now convert generic header clauses into requirements.
List reqList = new ArrayList();
for (int clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
for (int pathIdx = 0;
pathIdx < clauses.get(clauseIdx).m_paths.size();
pathIdx++)
{
// Prepend the package name to the array of attributes.
List<Attribute> attrs = clauses.get(clauseIdx).m_attrs;
List<Attribute> newAttrs = new ArrayList<Attribute>(attrs.size() + 1);
newAttrs.add(new Attribute(
Capability.PACKAGE_ATTR,
clauses.get(clauseIdx).m_paths.get(pathIdx), false));
newAttrs.addAll(attrs);
// Create package requirement and add to requirement list.
reqList.add(
new RequirementImpl(
Capability.PACKAGE_NAMESPACE,
clauses.get(clauseIdx).m_dirs,
newAttrs));
}
}
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.
Map<String, Attribute> attrMap = new HashMap();
for (int clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
// Put attributes for current clause in a map for easy lookup.
attrMap.clear();
for (int attrIdx = 0;
attrIdx < clauses.get(clauseIdx).m_attrs.size();
attrIdx++)
{
Attribute attr = clauses.get(clauseIdx).m_attrs.get(attrIdx);
attrMap.put(attr.getName(), attr);
}
// Check for "version" and "specification-version" attributes
// and verify they are the same if both are specified.
Attribute v = attrMap.get(Constants.VERSION_ATTRIBUTE);
Attribute sv = 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 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))
{
attrMap.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
v = (v == null) ? sv : v;
attrMap.put(Constants.VERSION_ATTRIBUTE,
new Attribute(
Constants.VERSION_ATTRIBUTE,
VersionRange.parse(v.getValue().toString()),
v.isMandatory()));
}
// If bundle version is specified, then convert its type to VersionRange.
v = attrMap.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
if (v != null)
{
attrMap.put(Constants.BUNDLE_VERSION_ATTRIBUTE,
new Attribute(
Constants.BUNDLE_VERSION_ATTRIBUTE,
VersionRange.parse(v.getValue().toString()),
v.isMandatory()));
}
// Re-copy the attributes in case they changed.
clauses.get(clauseIdx).m_attrs.clear();
clauses.get(clauseIdx).m_attrs.addAll(attrMap.values());
}
// Dynamic imports can have duplicates, so just check for import
// of java.*.
for (int clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
// Verify that java.* packages are not imported.
List<String> paths = clauses.get(clauseIdx).m_paths;
for (int pathIdx = 0; pathIdx < paths.size(); pathIdx++)
{
String pkgName = paths.get(pathIdx);
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);
}
}
}
if (!mv.equals("2"))
{
// 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 clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
// R3 bundles cannot have directives on their imports.
if (clauses.get(clauseIdx).m_dirs.size() != 0)
{
throw new BundleException("R3 imports cannot contain directives.");
}
}
}
return clauses;
}
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 (int clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
// Verify that the named package has not already been declared.
for (int pathIdx = 0; pathIdx < clauses.get(clauseIdx).m_paths.size(); pathIdx++)
{
// Verify that java.* packages are not exported.
if (clauses.get(clauseIdx).m_paths.get(pathIdx).startsWith("java."))
{
throw new BundleException(
"Exporting java.* packages not allowed: "
+ clauses.get(clauseIdx).m_paths.get(pathIdx));
}
else if (clauses.get(clauseIdx).m_paths.get(pathIdx).length() == 0)
{
throw new BundleException(
"Exported package names cannot be zero length.");
}
}
}
// If both version and specification-version attributes are specified,
// then verify that the values are equal.
Map<String, Attribute> attrMap = new HashMap();
for (int clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
// Put attributes for current clause in a map for easy lookup.
attrMap.clear();
for (int attrIdx = 0;
attrIdx < clauses.get(clauseIdx).m_attrs.size();
attrIdx++)
{
Attribute attr = clauses.get(clauseIdx).m_attrs.get(attrIdx);
attrMap.put(attr.getName(), attr);
}
// Check for "version" and "specification-version" attributes
// and verify they are the same if both are specified.
Attribute v = attrMap.get(Constants.VERSION_ATTRIBUTE);
Attribute sv = 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 specification-version are specified, but they are not equal.");
}
}
// Always add the default version if not specified.
if ((v == null) && (sv == null))
{
v = new Attribute(
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 Attribute(
Constants.VERSION_ATTRIBUTE,
Version.parseVersion(v.getValue().toString()),
v.isMandatory()));
// Re-copy the attributes since they have changed.
clauses.get(clauseIdx).m_attrs.clear();
clauses.get(clauseIdx).m_attrs.addAll(attrMap.values());
}
}
// If this is an R4 bundle, then make sure it doesn't specify
// bundle symbolic name or bundle version attributes.
if (mv.equals("2"))
{
for (int clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
// R3 package capabilities should only have a version attribute.
List<Attribute> attrs = clauses.get(clauseIdx).m_attrs;
for (int attrIdx = 0; attrIdx < attrs.size(); attrIdx++)
{
// Find symbolic name and version attribute, if present.
if (attrs.get(attrIdx).getName().equals(Constants.BUNDLE_VERSION_ATTRIBUTE) ||
attrs.get(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.
attrs.add(new Attribute(
Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, bsn, false));
attrs.add(new Attribute(
Constants.BUNDLE_VERSION_ATTRIBUTE, bv, false));
((ArrayList) attrs).trimToSize();
}
}
else if (!mv.equals("2"))
{
// 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 clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
// R3 bundles cannot have directives on their exports.
if (clauses.get(clauseIdx).m_dirs.size() != 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 (clauses.get(clauseIdx).m_attrs.size() != 0)
{
// R3 package capabilities should only have a version attribute.
List<Attribute> attrs = clauses.get(clauseIdx).m_attrs;
Attribute pkgVersion = new Attribute(Capability.VERSION_ATTR, Version.emptyVersion, false);
for (int attrIdx = 0; attrIdx < attrs.size(); attrIdx++)
{
if (attrs.get(attrIdx).getName().equals(Capability.VERSION_ATTR))
{
pkgVersion = attrs.get(attrIdx);
}
else
{
logger.log(
Logger.LOG_WARNING,
"Unknown R3 export attribute: "
+ attrs.get(attrIdx).getName());
}
}
// Recreate the export to remove any other attributes
// and add version if missing.
List<Attribute> newAttrs = new ArrayList<Attribute>(2);
newAttrs.add(pkgVersion);
clauses.set(clauseIdx, new ParsedHeaderClause(
clauses.get(clauseIdx).m_paths,
clauses.get(clauseIdx).m_dirs,
newAttrs));
}
}
}
return clauses;
}
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<Capability> getCapabilities()
{
return m_capabilities;
}
public List<Requirement> getRequirements()
{
return m_requirements;
}
public List<Requirement> getDynamicRequirements()
{
return m_dynamicRequirements;
}
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 (int i = 0; i < m_libraryClauses.size(); i++)
{
if (m_libraryClauses.get(i).match(m_configMap))
{
clauseList.add(m_libraryClauses.get(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.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.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 static List<ParsedHeaderClause> calculateImplicitImports(
List<Capability> 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).getAttribute(Capability.PACKAGE_ATTR).getValue()) == null)
{
// Convert Version to VersionRange.
List<Attribute> attrs = new ArrayList<Attribute>(exports.get(i).getAttributes());
for (int attrIdx = 0; (attrs != null) && (attrIdx < attrs.size()); attrIdx++)
{
if (attrs.get(attrIdx).getName().equals(Constants.VERSION_ATTRIBUTE))
{
attrs.set(attrIdx, new Attribute(
attrs.get(attrIdx).getName(),
VersionRange.parse(attrs.get(attrIdx).getValue().toString()),
attrs.get(attrIdx).isMandatory()));
}
}
List<String> paths = new ArrayList();
paths.add((String)
exports.get(i).getAttribute(Capability.PACKAGE_ATTR).getValue());
clauseList.add(
new ParsedHeaderClause(paths, new ArrayList<Directive>(0), attrs));
}
}
return clauseList;
}
private static List<Capability> calculateImplicitUses(
List<Capability> 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);
}
}
Directive uses = new Directive(
Constants.USES_DIRECTIVE, usesValue);
for (int i = 0; i < exports.size(); i++)
{
List<Directive> dirList = new ArrayList<Directive>(1);
dirList.add(uses);
exports.set(i, new CapabilityImpl(
exports.get(i).getModule(),
Capability.PACKAGE_NAMESPACE,
dirList,
exports.get(i).getAttributes()));
}
return exports;
}
private static boolean checkExtensionBundle(Map headerMap) throws BundleException
{
Directive extension = parseExtensionBundleHeader(
(String) 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'");
}
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 Capability parseBundleSymbolicName(Module 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 module capability and return it.
String symName = (String) clauses.get(0).m_paths.get(0);
List<Attribute> attrs = new ArrayList<Attribute>(2);
attrs.add(new Attribute(
Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, symName, false));
attrs.add(new Attribute(
Constants.BUNDLE_VERSION_ATTRIBUTE, bundleVersion, false));
return new CapabilityImpl(
owner,
Capability.MODULE_NAMESPACE,
clauses.get(0).m_dirs,
attrs);
}
return null;
}
private static List<Requirement> parseFragmentHost(Logger logger, Map headerMap)
throws BundleException
{
List<Requirement> 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));
}
// If the bundle version matching attribute is specified, then
// convert it to the proper type.
for (int attrIdx = 0;
attrIdx < clauses.get(0).m_attrs.size();
attrIdx++)
{
Attribute attr = clauses.get(0).m_attrs.get(attrIdx);
if (attr.getName().equals(Constants.BUNDLE_VERSION_ATTRIBUTE))
{
clauses.get(0).m_attrs.set(attrIdx,
new Attribute(
Constants.BUNDLE_VERSION_ATTRIBUTE,
VersionRange.parse(attr.getValue().toString()),
attr.isMandatory()));
}
}
// Prepend the host symbolic name to the array of attributes.
List<Attribute> attrs = clauses.get(0).m_attrs;
List<Attribute> newAttrs = new ArrayList<Attribute>(attrs.size() + 1);
newAttrs.add(new Attribute(
Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE,
clauses.get(0).m_paths.get(0), false));
newAttrs.addAll(attrs);
reqs.add(new RequirementImpl(Capability.HOST_NAMESPACE,
clauses.get(0).m_dirs,
newAttrs));
}
}
else
{
logger.log(Logger.LOG_WARNING, "Only R4 bundles can be fragments.");
}
return reqs;
}
public static List<Capability> parseExportHeader(
Logger logger, Module owner, String header, String bsn, Version bv)
{
List<Capability> 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<Capability> convertExports(
List<ParsedHeaderClause> clauses, Module owner)
{
List<Capability> capList = new ArrayList();
for (int clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
for (int pathIdx = 0;
pathIdx < clauses.get(clauseIdx).m_paths.size();
pathIdx++)
{
// Prepend the package name to the array of attributes.
List<Attribute> attrs = clauses.get(clauseIdx).m_attrs;
List<Attribute> newAttrs = new ArrayList<Attribute>(attrs.size() + 1);
newAttrs.add(new Attribute(
Capability.PACKAGE_ATTR,
clauses.get(clauseIdx).m_paths.get(pathIdx), false));
newAttrs.addAll(attrs);
// Create package capability and add to capability list.
capList.add(
new CapabilityImpl(
owner,
Capability.PACKAGE_NAMESPACE,
clauses.get(clauseIdx).m_dirs,
newAttrs));
}
}
return capList;
}
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 (int clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
for (int attrIdx = 0;
attrIdx < clauses.get(clauseIdx).m_attrs.size();
attrIdx++)
{
Attribute attr = clauses.get(clauseIdx).m_attrs.get(attrIdx);
if (attr.getName().equals(Constants.BUNDLE_VERSION_ATTRIBUTE))
{
clauses.get(clauseIdx).m_attrs.set(attrIdx,
new Attribute(
Constants.BUNDLE_VERSION_ATTRIBUTE,
VersionRange.parse(attr.getValue().toString()),
attr.isMandatory()));
}
}
}
}
return clauses;
}
private static List<Requirement> convertRequires(List<ParsedHeaderClause> clauses)
{
List<Requirement> reqList = new ArrayList();
for (int clauseIdx = 0; clauseIdx < clauses.size(); clauseIdx++)
{
List<Attribute> attrs = clauses.get(clauseIdx).m_attrs;
for (int pathIdx = 0;
pathIdx < clauses.get(clauseIdx).m_paths.size();
pathIdx++)
{
// Prepend the symbolic name to the array of attributes.
List<Attribute> newAttrs = new ArrayList<Attribute>(attrs.size() + 1);
newAttrs.add(new Attribute(
Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE,
clauses.get(clauseIdx).m_paths.get(pathIdx), false));
newAttrs.addAll(attrs);
// Create package requirement and add to requirement list.
reqList.add(
new RequirementImpl(
Capability.MODULE_NAMESPACE,
clauses.get(clauseIdx).m_dirs,
newAttrs));
}
}
return reqList;
}
public static Directive parseExtensionBundleHeader(String header)
throws BundleException
{
List<ParsedHeaderClause> clauses = parseStandardHeader(header);
Directive result = null;
if (clauses.size() == 1)
{
// See if there is the "extension" directive.
List<Directive> dirs = clauses.get(0).m_dirs;
for (int dirIdx = 0; (result == null) && (dirIdx < dirs.size()); dirIdx++)
{
if (Constants.EXTENSION_DIRECTIVE.equals(dirs.get(dirIdx).getName()))
{
// 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)))
{
result = (Directive) dirs.get(dirIdx);
}
else
{
throw new BundleException(
"Only the system bundle can have extension bundles.");
}
}
}
}
return result;
}
private void parseActivationPolicy(Map headerMap)
{
m_activationPolicy = Module.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 (int clauseIdx = 0; clauseIdx < clauses.get(0).m_paths.size(); clauseIdx++)
{
if (clauses.get(0).m_paths.get(clauseIdx).equals(Constants.ACTIVATION_LAZY))
{
m_activationPolicy = Module.LAZY_ACTIVATION;
for (int dirIdx = 0; dirIdx < clauses.get(0).m_dirs.size(); dirIdx++)
{
Directive dir = clauses.get(0).m_dirs.get(dirIdx);
if (dir.getName().equalsIgnoreCase(Constants.INCLUDE_DIRECTIVE))
{
m_activationIncludeDir = (String) dir.getValue();
}
else if (dir.getName().equalsIgnoreCase(Constants.EXCLUDE_DIRECTIVE))
{
m_activationExcludeDir = (String) 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 List<ParsedHeaderClause> parseStandardHeader(String header)
{
List<ParsedHeaderClause> clauses = new ArrayList();
if (header != null)
{
if (header.length() == 0)
{
throw new IllegalArgumentException(
"A header cannot be an empty string.");
}
List<String> clauseStrings = parseDelimitedString(
header, FelixConstants.CLASS_PATH_SEPARATOR);
for (int i = 0; (clauseStrings != null) && (i < clauseStrings.size()); i++)
{
clauses.add(parseStandardHeaderClause(clauseStrings.get(i)));
}
}
return clauses;
}
// Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
private static ParsedHeaderClause parseStandardHeaderClause(String clauseString)
throws IllegalArgumentException
{
// Break string into semi-colon delimited pieces.
List<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.size(); pieceIdx++)
{
if (pieces.get(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.
List<String> paths = new ArrayList<String>(pathCount);
for (int pathIdx = 0; pathIdx < pathCount; pathIdx++)
{
paths.add(pieces.get(pathIdx));
}
// Parse the directives/attributes.
Map<String, Directive> dirsMap = new HashMap();
Map<String, Attribute> attrsMap = new HashMap();
int idx = -1;
String sep = null;
for (int pieceIdx = pathCount; pieceIdx < pieces.size(); pieceIdx++)
{
// Check if it is a directive.
if ((idx = pieces.get(pieceIdx).indexOf(FelixConstants.DIRECTIVE_SEPARATOR)) >= 0)
{
sep = FelixConstants.DIRECTIVE_SEPARATOR;
}
// Check if it is an attribute.
else if ((idx = pieces.get(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.get(pieceIdx).substring(0, idx).trim();
String value = pieces.get(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 Directive(key, value));
}
else
{
// Check for duplicates.
if (attrsMap.get(key) != null)
{
throw new IllegalArgumentException(
"Duplicate attribute: " + key);
}
attrsMap.put(key, new Attribute(key, value, false));
}
}
List<Directive> dirs = new ArrayList<Directive>(dirsMap.size());
for (Entry<String, Directive> entry : dirsMap.entrySet())
{
dirs.add(entry.getValue());
}
List<Attribute> attrs = new ArrayList<Attribute>(attrsMap.size());
for (Entry<String, Attribute> entry : attrsMap.entrySet())
{
attrs.add(entry.getValue());
}
return new ParsedHeaderClause(paths, dirs, attrs);
}
/**
* 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 List<String> parseDelimitedString(String value, String delim)
{
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);
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 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;
}
}