| /* |
| * 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()]); |
| } |
| } |