You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by ri...@apache.org on 2009/08/25 22:30:34 UTC

svn commit: r807795 [4/5] - in /felix/sandbox/rickhall/resolver: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/felix/ src/main/java/org/apache/felix/resolver/ src/main/java/org/apache/felix/resol...

Added: felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/ManifestParser.java
URL: http://svn.apache.org/viewvc/felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/ManifestParser.java?rev=807795&view=auto
==============================================================================
--- felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/ManifestParser.java (added)
+++ felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/ManifestParser.java Tue Aug 25 20:30:33 2009
@@ -0,0 +1,1223 @@
+/*
+ * 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.resolver.manifestparser;
+
+import org.apache.felix.resolver.VersionRange;
+import org.apache.felix.resolver.Version;
+import java.util.*;
+
+public class ManifestParser
+{
+    final static int EAGER_ACTIVATION = 0;
+    final static int LAZY_ACTIVATION = 1;
+
+    private final Map m_headerMap;
+    private volatile int m_activationPolicy = 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 Capability[] m_capabilities;
+    private volatile Requirement[] m_requirements;
+    private volatile Requirement[] m_dynamicRequirements;
+    private volatile R4LibraryClause[] m_libraryHeaders;
+    private volatile boolean m_libraryHeadersOptional = false;
+
+    public ManifestParser(Map headerMap)
+        throws Exception
+    {
+        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 Exception(
+                "Unknown 'Bundle-ManifestVersion' value: " + manifestVersion);
+        }
+
+        // Create lists to hold capabilities and requirements.
+        List capList = new ArrayList();
+        List reqList = 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(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(
+                    Capability.HOST_NAMESPACE, null,
+                    ((Capability) moduleCap).getAttributes()));
+            }
+        }
+
+        //
+        // Parse Fragment-Host.
+        //
+        Object[][][] clauses = parseStandardHeader(
+            (String) headerMap.get(Constants.FRAGMENT_HOST));
+        if (clauses.length > 0)
+        {
+            // IGNORE FRAGMENT-HOST FOR NOW!
+        }
+
+        //
+        // Parse Export-Package.
+        //
+
+        // Get exported packages from bundle manifest.
+        Capability[] 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(Capability.PACKAGE_PROPERTY);
+            // Verify that java.* packages are not exported.
+            if (pkgName.startsWith("java."))
+            {
+                throw new Exception(
+                    "Exporting java.* packages not allowed: " + pkgName);
+            }
+            capList.add(exportCaps[capIdx]);
+        }
+
+        // Create an array of all capabilities.
+        m_capabilities = (Capability[]) capList.toArray(new Capability[capList.size()]);
+
+        //
+        // Parse Require-Bundle
+        //
+
+        Requirement[] 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.
+        Requirement[] importReqs = parseImportHeader(
+            (String) headerMap.get(Constants.IMPORT_PACKAGE));
+
+        // Verify there are no duplicate import declarations.
+        Set dupeSet = new HashSet();
+        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 (!dupeSet.contains(pkgName))
+            {
+                // Verify that java.* packages are not imported.
+                if (pkgName.startsWith("java."))
+                {
+                    throw new Exception(
+                        "Importing java.* packages not allowed: " + pkgName);
+                }
+                dupeSet.add(pkgName);
+            }
+            else
+            {
+                throw new Exception("Duplicate import - " + pkgName);
+            }
+            // If it has not already been imported, then add it to the list
+            // of requirements.
+            reqList.add(importReqs[reqIdx]);
+        }
+
+        // Create an array of all requirements.
+        m_requirements = (Requirement[]) reqList.toArray(new Requirement[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 Exception(
+                    "Dynamically importing java.* packages not allowed: " + pkgName);
+            }
+            else if (!pkgName.equals("*") && pkgName.endsWith("*") && !pkgName.endsWith(".*"))
+            {
+                throw new Exception(
+                    "Partial package name wild carding is not allowed: " + pkgName);
+            }
+        }
+
+        //
+        // Parse Bundle-NativeCode.
+        //
+
+        // Get native library entry names for module library sources.
+        m_libraryHeaders =
+            parseLibraryStrings(
+                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 Capability[] getCapabilities()
+    {
+        return m_capabilities;
+    }
+
+    public Requirement[] getRequirements()
+    {
+        return m_requirements;
+    }
+
+    public Requirement[] getDynamicRequirements()
+    {
+        return m_dynamicRequirements;
+    }
+
+    public R4LibraryClause[] getLibraryClauses()
+    {
+        return m_libraryHeaders;
+    }
+
+    private String getName(String path)
+    {
+        int idx = path.lastIndexOf('/');
+        if (idx > -1)
+        {
+            return path.substring(idx);
+        }
+        return path;
+    }
+
+    private void checkAndNormalizeR3() throws Exception
+    {
+        // 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(Capability.PACKAGE_NAMESPACE))
+            {
+                // R3 bundles cannot have directives on their exports.
+                if (((Capability) m_capabilities[capIdx]).getDirectives().length != 0)
+                {
+                    throw new Exception("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(Capability.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(Capability.PACKAGE_PROPERTY))
+                        {
+                            pkgName = ((Capability) m_capabilities[capIdx]).getAttributes()[attrIdx];
+                        }
+                        else if (((Capability) m_capabilities[capIdx]).getAttributes()[attrIdx]
+                            .getName().equals(Capability.VERSION_PROPERTY))
+                        {
+                            pkgVersion = ((Capability) m_capabilities[capIdx]).getAttributes()[attrIdx];
+                        }
+                        else
+                        {
+                            System.err.println(
+                                "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(
+                        Capability.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(Capability.PACKAGE_NAMESPACE))
+            {
+                // R3 bundles cannot have directives on their imports.
+                if (((Requirement) m_requirements[reqIdx]).getDirectives().length != 0)
+                {
+                    throw new Exception("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(Capability.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(Capability.PACKAGE_PROPERTY))
+                        {
+                            pkgName = ((Requirement) m_requirements[reqIdx]).getAttributes()[attrIdx];
+                        }
+                        else if (((Requirement) m_requirements[reqIdx]).getAttributes()[attrIdx]
+                          .getName().equals(Capability.VERSION_PROPERTY))
+                        {
+                            pkgVersion = ((Requirement) m_requirements[reqIdx]).getAttributes()[attrIdx];
+                        }
+                        else
+                        {
+                            System.out.println(
+                                "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(
+                        Capability.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(Capability.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(Capability.PACKAGE_NAMESPACE) &&
+                (map.get(m_capabilities[i].getProperties().get(Capability.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(Capability.PACKAGE_PROPERTY),
+                    new Requirement(Capability.PACKAGE_NAMESPACE, null, attrs));
+            }
+        }
+        m_requirements =
+            (Requirement[]) map.values().toArray(new Requirement[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(Capability.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(Capability.PACKAGE_NAMESPACE))
+            {
+                m_capabilities[i] = new Capability(
+                    Capability.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 Exception("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 Exception
+    {
+        // Verify that bundle symbolic name is specified.
+        if (m_bundleSymbolicName == null)
+        {
+            throw new Exception("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 Exception(
+                    "Extension bundle must have either 'extension:=framework' or 'extension:=bootclasspath'");
+            }
+            checkExtensionBundle();
+            m_isExtension = true;
+        }
+    }
+
+    private static Capability[] checkAndNormalizeR4Exports(
+        Capability[] caps, String bsn, Version bv)
+        throws Exception
+    {
+        // 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(Capability.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 Exception(
+                            "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(
+                    Capability.PACKAGE_NAMESPACE,
+                    ((Capability) caps[i]).getDirectives(),
+                    newAttrs);
+            }
+        }
+
+        return caps;
+    }
+
+    private void checkExtensionBundle() throws Exception
+    {
+        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 Exception("Invalid extension bundle manifest");
+        }
+    }
+
+    private static Capability parseBundleSymbolicName(Map headerMap)
+        throws Exception
+    {
+        Object[][][] clauses = parseStandardHeader(
+            (String) headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
+        if (clauses.length > 0)
+        {
+            if (clauses.length > 1)
+            {
+                throw new Exception(
+                    "Cannot have multiple symbolic names: "
+                        + headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
+            }
+            else if (clauses[0][CLAUSE_PATHS_INDEX].length > 1)
+            {
+                throw new Exception(
+                    "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(Capability.MODULE_NAMESPACE, (R4Directive[]) clauses[0][CLAUSE_DIRECTIVES_INDEX], attrs);
+        }
+
+        return null;
+    }
+
+    public static Capability[] parseExportHeader(String header, String bsn, Version bv)
+        throws Exception
+    {
+        Capability[] caps = parseExportHeader(header);
+        try
+        {
+            caps = checkAndNormalizeR4Exports(caps, bsn, bv);
+        }
+        catch (Exception ex)
+        {
+            caps = null;
+        }
+        return caps;
+    }
+
+    private static Capability[] 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(
+                    Capability.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(
+                        Capability.PACKAGE_NAMESPACE,
+                        (R4Directive[]) clauses[clauseIdx][CLAUSE_DIRECTIVES_INDEX],
+                        newAttrs));
+            }
+        }
+
+        return (Capability[]) capList.toArray(new Capability[capList.size()]);
+    }
+
+    private static Requirement[] 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(
+                    Capability.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(
+                        Capability.PACKAGE_NAMESPACE,
+                        (R4Directive[]) clauses[clauseIdx][CLAUSE_DIRECTIVES_INDEX],
+                        newAttrs));
+            }
+        }
+
+        return (Requirement[]) reqList.toArray(new Requirement[reqList.size()]);
+    }
+
+    private static Requirement[] 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(
+                        Capability.MODULE_NAMESPACE,
+                        (R4Directive[]) clauses[clauseIdx][CLAUSE_DIRECTIVES_INDEX],
+                        newAttrs));
+            }
+        }
+
+        return (Requirement[]) reqList.toArray(new Requirement[reqList.size()]);
+    }
+
+    public static R4Directive parseExtensionBundleHeader(String header)
+        throws Exception
+    {
+        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 ("org.apache.felix.framework".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 Exception(
+                            "Only the system bundle can have extension bundles.");
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+    private void parseActivationPolicy(Map headerMap)
+    {
+        m_activationPolicy = 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 = 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, ",");
+
+            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, ";");
+
+        // 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(":=")) >= 0)
+            {
+                sep = ":=";
+            }
+            // Check if it is an attribute.
+            else if ((idx = pieces[pieceIdx].indexOf("=")) >= 0)
+            {
+                sep = "=";
+            }
+            // 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(":="))
+            {
+                // 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(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(libStrs[i]);
+            libList.add(clause);
+        }
+
+        return (R4LibraryClause[]) libList.toArray(new R4LibraryClause[libList.size()]);
+    }
+}

Added: felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4Attribute.java
URL: http://svn.apache.org/viewvc/felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4Attribute.java?rev=807795&view=auto
==============================================================================
--- felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4Attribute.java (added)
+++ felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4Attribute.java Tue Aug 25 20:30:33 2009
@@ -0,0 +1,48 @@
+/*
+ * 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.resolver.manifestparser;
+
+public class R4Attribute
+{
+    private final String m_name;
+    private final Object m_value;
+    private final boolean m_isMandatory;
+
+    public R4Attribute(String name, Object value, boolean isMandatory)
+    {
+        m_name = name;
+        m_value = value;
+        m_isMandatory = isMandatory;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public Object getValue()
+    {
+        return m_value;
+    }
+
+    public boolean isMandatory()
+    {
+        return m_isMandatory;
+    }
+}
\ No newline at end of file

Added: felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4Directive.java
URL: http://svn.apache.org/viewvc/felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4Directive.java?rev=807795&view=auto
==============================================================================
--- felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4Directive.java (added)
+++ felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4Directive.java Tue Aug 25 20:30:33 2009
@@ -0,0 +1,41 @@
+/*
+ * 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.resolver.manifestparser;
+
+public class R4Directive
+{
+    private final String m_name;
+    private final String m_value;
+
+    public R4Directive(String name, String value)
+    {
+        m_name = name;
+        m_value = value;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public String getValue()
+    {
+        return m_value;
+    }
+}
\ No newline at end of file

Added: felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4Library.java
URL: http://svn.apache.org/viewvc/felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4Library.java?rev=807795&view=auto
==============================================================================
--- felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4Library.java (added)
+++ felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4Library.java Tue Aug 25 20:30:33 2009
@@ -0,0 +1,168 @@
+/*
+ * 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.resolver.manifestparser;
+
+import java.util.Map;
+
+public class R4Library
+{
+    private String m_libraryFile;
+    private String[] m_osnames;
+    private String[] m_processors;
+    private String[] m_osversions;
+    private String[] m_languages;
+    private String m_selectionFilter;
+
+    public R4Library(
+        String libraryFile, String[] osnames, String[] processors, String[] osversions,
+        String[] languages, String selectionFilter) throws Exception
+    {
+        m_libraryFile = libraryFile;
+        m_osnames = osnames;
+        m_processors = processors;
+        m_osversions = osversions;
+        m_languages = languages;
+        m_selectionFilter = selectionFilter;
+    }
+
+    public String getEntryName()
+    {
+        return m_libraryFile;
+    }
+
+    public String[] getOSNames()
+    {
+        return m_osnames;
+    }
+
+    public String[] getProcessors()
+    {
+        return m_processors;
+    }
+
+    public String[] getOSVersions()
+    {
+        return m_osversions;
+    }
+
+    public String[] getLanguages()
+    {
+        return m_languages;
+    }
+
+    public String getSelectionFilter()
+    {
+        return m_selectionFilter;
+    }
+
+    /**
+     * <p>
+     * Determines if the specified native library name matches this native
+     * library definition.
+     * </p>
+     * @param name the native library name to try to match.
+     * @return <tt>true</tt> if this native library name matches this native
+     *         library definition; <tt>false</tt> otherwise.
+    **/
+    public boolean match(Map configMap, String name)
+    {
+        String libname = System.mapLibraryName(name);
+        String[] exts = ManifestParser.parseDelimitedString(
+            (String) configMap.get(Constants.FRAMEWORK_LIBRARY_EXTENSIONS), ",");
+        int extIdx = 0;
+
+        // First try to match the default name, then try to match any additionally
+        // specified library extensions.
+        do
+        {
+            if (m_libraryFile.equals(libname) || m_libraryFile.endsWith("/" + libname))
+            {
+                return true;
+            }
+            else if (libname.endsWith(".jnilib") && m_libraryFile.endsWith(".dylib"))
+            {
+                libname = libname.substring(0, libname.length() - 6) + "dylib";
+                if (m_libraryFile.equals(libname) || m_libraryFile.endsWith("/" + libname))
+                {
+                    return true;
+                }
+            }
+            else if (m_libraryFile.equals(name) || m_libraryFile.endsWith("/" + name))
+            {
+                return true;
+            }
+
+            int idx = libname.lastIndexOf(".");
+            libname = (idx < 0)
+                ? libname + "." + exts[extIdx++]
+                : libname.substring(0, idx) + "." + exts[extIdx++];
+        }
+        while ((exts != null) && (extIdx < exts.length));
+
+        return false;
+    }
+
+    public String toString()
+    {
+        if (m_libraryFile != null)
+        {
+            StringBuffer sb = new StringBuffer();
+            sb.append(m_libraryFile);
+            for (int i = 0; (m_osnames != null) && (i < m_osnames.length); i++)
+            {
+                sb.append(';');
+                sb.append(Constants.BUNDLE_NATIVECODE_OSNAME);
+                sb.append('=');
+                sb.append(m_osnames[i]);
+            }
+            for (int i = 0; (m_processors != null) && (i < m_processors.length); i++)
+            {
+                sb.append(';');
+                sb.append(Constants.BUNDLE_NATIVECODE_PROCESSOR);
+                sb.append('=');
+                sb.append(m_processors[i]);
+            }
+            for (int i = 0; (m_osversions != null) && (i < m_osversions.length); i++)
+            {
+                sb.append(';');
+                sb.append(Constants.BUNDLE_NATIVECODE_OSVERSION);
+                sb.append('=');
+                sb.append(m_osversions[i]);
+            }
+            for (int i = 0; (m_languages != null) && (i < m_languages.length); i++)
+            {
+                sb.append(';');
+                sb.append(Constants.BUNDLE_NATIVECODE_LANGUAGE);
+                sb.append('=');
+                sb.append(m_languages[i]);
+            }
+            if (m_selectionFilter != null)
+            {
+                sb.append(';');
+                sb.append(Constants.SELECTION_FILTER_ATTRIBUTE);
+                sb.append('=');
+                sb.append('\'');
+                sb.append(m_selectionFilter);
+            }
+
+            return sb.toString();
+        }
+        return "*";
+    }
+}
\ No newline at end of file

Added: felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4LibraryClause.java
URL: http://svn.apache.org/viewvc/felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4LibraryClause.java?rev=807795&view=auto
==============================================================================
--- felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4LibraryClause.java (added)
+++ felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/R4LibraryClause.java Tue Aug 25 20:30:33 2009
@@ -0,0 +1,490 @@
+/*
+ * 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.resolver.manifestparser;
+
+import org.apache.felix.resolver.VersionRange;
+import org.apache.felix.resolver.Version;
+import java.util.*;
+
+public class R4LibraryClause
+{
+    private final String[] m_libraryEntries;
+    private final String[] m_osnames;
+    private final String[] m_processors;
+    private final String[] m_osversions;
+    private final String[] m_languages;
+    private final String m_selectionFilter;
+
+    public R4LibraryClause(String[] libraryEntries, String[] osnames,
+        String[] processors, String[] osversions, String[] languages,
+        String selectionFilter)
+    {
+        m_libraryEntries = libraryEntries;
+        m_osnames = osnames;
+        m_processors = processors;
+        m_osversions = osversions;
+        m_languages = languages;
+        m_selectionFilter = selectionFilter;
+    }
+
+    public R4LibraryClause(R4LibraryClause library)
+    {
+        m_libraryEntries = library.m_libraryEntries;
+        m_osnames = library.m_osnames;
+        m_osversions = library.m_osversions;
+        m_processors = library.m_processors;
+        m_languages = library.m_languages;
+        m_selectionFilter = library.m_selectionFilter;
+    }
+
+    public String[] getLibraryEntries()
+    {
+        return m_libraryEntries;
+    }
+
+    public String[] getOSNames()
+    {
+        return m_osnames;
+    }
+
+    public String[] getProcessors()
+    {
+        return m_processors;
+    }
+
+    public String[] getOSVersions()
+    {
+        return m_osversions;
+    }
+
+    public String[] getLanguages()
+    {
+        return m_languages;
+    }
+
+    public String getSelectionFilter()
+    {
+        return m_selectionFilter;
+    }
+
+    public boolean match(Map configMap) throws Exception
+    {
+        String normal_osname = normalizeOSName((String) configMap.get(Constants.FRAMEWORK_OS_NAME));
+        String normal_processor = normalizeProcessor((String) configMap.get(Constants.FRAMEWORK_PROCESSOR));
+        String normal_osversion = normalizeOSVersion((String) configMap.get(Constants.FRAMEWORK_OS_VERSION));
+        String normal_language = (String) configMap.get(Constants.FRAMEWORK_LANGUAGE);
+
+        // Check library's osname.
+        if (!checkOSNames(normal_osname, getOSNames()))
+        {
+            return false;
+        }
+
+        // Check library's processor.
+        if (!checkProcessors(normal_processor, getProcessors()))
+        {
+            return false;
+        }
+
+        // Check library's osversion if specified.
+        if ((getOSVersions() != null) &&
+            (getOSVersions().length > 0) &&
+            !checkOSVersions(normal_osversion, getOSVersions()))
+        {
+            return false;
+        }
+
+        // Check library's language if specified.
+        if ((getLanguages() != null) &&
+            (getLanguages().length > 0) &&
+            !checkLanguages(normal_language, getLanguages()))
+        {
+            return false;
+        }
+
+        // Check library's selection-filter if specified.
+        if ((getSelectionFilter() != null) &&
+            (getSelectionFilter().length() >= 0) &&
+            !checkSelectionFilter(configMap, getSelectionFilter()))
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean checkOSNames(String currentOSName, String[] osnames)
+    {
+        boolean win32 = currentOSName.startsWith("win") &&
+            (currentOSName.equals("windows95") ||
+            currentOSName.equals("windows98") ||
+            currentOSName.equals("windowsnt") ||
+            currentOSName.equals("windows2000") ||
+            currentOSName.equals("windowsxp") ||
+            currentOSName.equals("windowsce") ||
+            currentOSName.equals("windowsvista"));
+
+        for (int i = 0; (osnames != null) && (i < osnames.length); i++)
+        {
+            if (osnames[i].equals(currentOSName) ||
+                ("win32".equals(osnames[i]) && win32))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkProcessors(String currentProcessor, String[] processors)
+    {
+        for (int i = 0; (processors != null) && (i < processors.length); i++)
+        {
+            if (processors[i].equals(currentProcessor))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkOSVersions(String currentOSVersion, String[] osversions)
+        throws Exception
+    {
+        for (int i = 0; (osversions != null) && (i < osversions.length); i++)
+        {
+            try
+            {
+                VersionRange range = VersionRange.parse(osversions[i]);
+                if (range.isInRange(new Version(currentOSVersion)))
+                {
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(
+                    "Error evaluating osversion: " + osversions[i], ex);
+            }
+        }
+        return false;
+    }
+
+    private boolean checkLanguages(String currentLanguage, String[] languages)
+    {
+        for (int i = 0; (languages != null) && (i < languages.length); i++)
+        {
+            if (languages[i].equals(currentLanguage))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkSelectionFilter(Map configMap, String expr)
+        throws Exception
+    {
+        return false;
+    }
+
+    public static R4LibraryClause parse(String s)
+    {
+        try
+        {
+            if ((s == null) || (s.length() == 0))
+            {
+                return null;
+            }
+
+            if (s.equals("*"))
+            {
+                return new R4LibraryClause(null, null, null, null, null, null);
+            }
+
+            // The tokens are separated by semicolons and may include
+            // any number of libraries along with one set of associated
+            // properties.
+            StringTokenizer st = new StringTokenizer(s, ";");
+            String[] libEntries = new String[st.countTokens()];
+            List osNameList = new ArrayList();
+            List osVersionList = new ArrayList();
+            List processorList = new ArrayList();
+            List languageList = new ArrayList();
+            String selectionFilter = null;
+            int libCount = 0;
+            while (st.hasMoreTokens())
+            {
+                String token = st.nextToken().trim();
+                if (token.indexOf('=') < 0)
+                {
+                    // Remove the slash, if necessary.
+                    libEntries[libCount] = (token.charAt(0) == '/')
+                        ? token.substring(1)
+                        : token;
+                    libCount++;
+                }
+                else
+                {
+                    // Check for valid native library properties; defined as
+                    // a property name, an equal sign, and a value.
+                    // NOTE: StringTokenizer can not be used here because
+                    // a value can contain one or more "=" too, e.g.,
+                    // selection-filter="(org.osgi.framework.windowing.system=gtk)"
+                    String property = null;
+                    String value = null;
+                    if (!(token.indexOf("=") > 1))
+                    {
+                        throw new IllegalArgumentException(
+                            "Bundle manifest native library entry malformed: " + token);
+                    }
+                    else
+                    {
+                        property = (token.substring(0, token.indexOf("=")))
+                            .trim().toLowerCase();
+                        value = (token.substring(token.indexOf("=") + 1, token
+                            .length())).trim();
+                    }
+
+                    // Values may be quoted, so remove quotes if present.
+                    if (value.charAt(0) == '"')
+                    {
+                        // This should always be true, otherwise the
+                        // value wouldn't be properly quoted, but we
+                        // will check for safety.
+                        if (value.charAt(value.length() - 1) == '"')
+                        {
+                            value = value.substring(1, value.length() - 1);
+                        }
+                        else
+                        {
+                            value = value.substring(1);
+                        }
+                    }
+                    // Add the value to its corresponding property list.
+                    if (property.equals(Constants.BUNDLE_NATIVECODE_OSNAME))
+                    {
+                        osNameList.add(normalizeOSName(value));
+                    }
+                    else if (property.equals(Constants.BUNDLE_NATIVECODE_OSVERSION))
+                    {
+                        osVersionList.add(normalizeOSVersion(value));
+                    }
+                    else if (property.equals(Constants.BUNDLE_NATIVECODE_PROCESSOR))
+                    {
+                        processorList.add(normalizeProcessor(value));
+                    }
+                    else if (property.equals(Constants.BUNDLE_NATIVECODE_LANGUAGE))
+                    {
+                        languageList.add(value);
+                    }
+                    else if (property.equals(Constants.SELECTION_FILTER_ATTRIBUTE))
+                    {
+// TODO: NATIVE - I believe we can have multiple selection filters too.
+                        selectionFilter = value;
+                    }
+                }
+            }
+
+            if (libCount == 0)
+            {
+                return null;
+            }
+
+            // Shrink lib file array.
+            String[] actualLibEntries = new String[libCount];
+            System.arraycopy(libEntries, 0, actualLibEntries, 0, libCount);
+            return new R4LibraryClause(
+                actualLibEntries,
+                (String[]) osNameList.toArray(new String[osNameList.size()]),
+                (String[]) processorList.toArray(new String[processorList.size()]),
+                (String[]) osVersionList.toArray(new String[osVersionList.size()]),
+                (String[]) languageList.toArray(new String[languageList.size()]),
+                selectionFilter);
+        }
+        catch (RuntimeException ex)
+        {
+            System.err.println("Error parsing native library header: " + ex);
+            throw ex;
+        }
+    }
+
+    public static String normalizeOSName(String value)
+    {
+        value = value.toLowerCase();
+
+        if (value.startsWith("win"))
+        {
+            String os = "win";
+            if (value.indexOf("32") >= 0 || value.indexOf("*") >= 0)
+            {
+                os = "win32";
+            }
+            else if (value.indexOf("95") >= 0)
+            {
+                os = "windows95";
+            }
+            else if (value.indexOf("98") >= 0)
+            {
+                os = "windows98";
+            }
+            else if (value.indexOf("nt") >= 0)
+            {
+                os = "windowsnt";
+            }
+            else if (value.indexOf("2000") >= 0)
+            {
+                os = "windows2000";
+            }
+            else if (value.indexOf("xp") >= 0)
+            {
+                os = "windowsxp";
+            }
+            else if (value.indexOf("ce") >= 0)
+            {
+                os = "windowsce";
+            }
+            else if (value.indexOf("vista") >= 0)
+            {
+                os = "windowsvista";
+            }
+            return os;
+        }
+        else if (value.startsWith("linux"))
+        {
+            return "linux";
+        }
+        else if (value.startsWith("aix"))
+        {
+            return "aix";
+        }
+        else if (value.startsWith("digitalunix"))
+        {
+            return "digitalunix";
+        }
+        else if (value.startsWith("hpux"))
+        {
+            return "hpux";
+        }
+        else if (value.startsWith("irix"))
+        {
+            return "irix";
+        }
+        else if (value.startsWith("macos") || value.startsWith("mac os"))
+        {
+            return "macos";
+        }
+        else if (value.startsWith("netware"))
+        {
+            return "netware";
+        }
+        else if (value.startsWith("openbsd"))
+        {
+            return "openbsd";
+        }
+        else if (value.startsWith("netbsd"))
+        {
+            return "netbsd";
+        }
+        else if (value.startsWith("os2") || value.startsWith("os/2"))
+        {
+            return "os2";
+        }
+        else if (value.startsWith("qnx") || value.startsWith("procnto"))
+        {
+            return "qnx";
+        }
+        else if (value.startsWith("solaris"))
+        {
+            return "solaris";
+        }
+        else if (value.startsWith("sunos"))
+        {
+            return "sunos";
+        }
+        else if (value.startsWith("vxworks"))
+        {
+            return "vxworks";
+        }
+        return value;
+    }
+
+    public static String normalizeProcessor(String value)
+    {
+        value = value.toLowerCase();
+
+        if (value.startsWith("x86-64") || value.startsWith("amd64"))
+        {
+            return "x86-64";
+        }
+        else if (value.startsWith("x86") || value.startsWith("pentium")
+            || value.startsWith("i386") || value.startsWith("i486")
+            || value.startsWith("i586") || value.startsWith("i686"))
+        {
+            return "x86";
+        }
+        else if (value.startsWith("68k"))
+        {
+            return "68k";
+        }
+        else if (value.startsWith("arm"))
+        {
+            return "arm";
+        }
+        else if (value.startsWith("alpha"))
+        {
+            return "alpha";
+        }
+        else if (value.startsWith("ignite") || value.startsWith("psc1k"))
+        {
+            return "ignite";
+        }
+        else if (value.startsWith("mips"))
+        {
+            return "mips";
+        }
+        else if (value.startsWith("parisc"))
+        {
+            return "parisc";
+        }
+        else if (value.startsWith("powerpc") || value.startsWith("power")
+            || value.startsWith("ppc"))
+        {
+            return "powerpc";
+        }
+        else if (value.startsWith("sparc"))
+        {
+            return "sparc";
+        }
+        return value;
+    }
+
+    public static String normalizeOSVersion(String value)
+    {
+        // Header: 'Bundle-NativeCode', Parameter: 'osversion'
+        // Standardized 'osversion': major.minor.micro, only digits
+        try
+        {
+            return VersionRange.parse(value).toString();
+        }
+        catch (Exception ex)
+        {
+            return Version.emptyVersion.toString();
+        }
+    }
+}
\ No newline at end of file

Added: felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/Requirement.java
URL: http://svn.apache.org/viewvc/felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/Requirement.java?rev=807795&view=auto
==============================================================================
--- felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/Requirement.java (added)
+++ felix/sandbox/rickhall/resolver/src/main/java/org/apache/felix/resolver/manifestparser/Requirement.java Tue Aug 25 20:30:33 2009
@@ -0,0 +1,244 @@
+/*
+ * 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.resolver.manifestparser;
+
+import org.apache.felix.resolver.VersionRange;
+import org.apache.felix.resolver.Version;
+
+public class Requirement
+{
+    private final String m_namespace;
+    private final R4Directive[] m_directives;
+    private final R4Attribute[] m_attributes;
+    private final boolean m_isOptional;
+
+    private final String m_targetName;
+    private final VersionRange m_targetVersionRange;
+
+    public Requirement(String namespace, R4Directive[] directives, R4Attribute[] attributes)
+    {
+        m_namespace = namespace;
+        m_directives = directives;
+        m_attributes = attributes;
+
+        // Find all import directives: resolution.
+        boolean optional = false;
+        for (int i = 0; (m_directives != null) && (i < m_directives.length); i++)
+        {
+            if (m_directives[i].getName().equals(Constants.RESOLUTION_DIRECTIVE))
+            {
+                optional = m_directives[i].getValue().equals(Constants.RESOLUTION_OPTIONAL);
+            }
+        }
+        m_isOptional = optional;
+
+        String targetName = null;
+        VersionRange targetVersionRange = VersionRange.infiniteRange;
+        for (int i = 0; i < m_attributes.length; i++)
+        {
+            if (m_namespace.equals(Capability.MODULE_NAMESPACE))
+            {
+                if (m_attributes[i].getName().equals(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE))
+                {
+                    targetName = (String) m_attributes[i].getValue();
+                }
+                else if (m_attributes[i].getName().equals(Constants.BUNDLE_VERSION_ATTRIBUTE))
+                {
+                    targetVersionRange = (VersionRange) m_attributes[i].getValue();
+                }
+            }
+            else if (m_namespace.equals(Capability.PACKAGE_NAMESPACE))
+            {
+                if (m_attributes[i].getName().equals(Capability.PACKAGE_PROPERTY))
+                {
+                    targetName = (String) m_attributes[i].getValue();
+                }
+                else if (m_attributes[i].getName().equals(Capability.VERSION_PROPERTY))
+                {
+                    targetVersionRange = (VersionRange) m_attributes[i].getValue();
+                }
+            }
+            else if (m_namespace.equals(Capability.HOST_NAMESPACE))
+            {
+                if (m_attributes[i].getName().equals(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE))
+                {
+                    targetName = (String) m_attributes[i].getValue();
+                }
+                else if (m_attributes[i].getName().equals(Constants.BUNDLE_VERSION_ATTRIBUTE))
+                {
+                    targetVersionRange = (VersionRange) m_attributes[i].getValue();
+                }
+            }
+        }
+        m_targetName = targetName;
+        m_targetVersionRange = targetVersionRange;
+    }
+
+    public String getNamespace()
+    {
+        return m_namespace;
+    }
+
+// TODO: RB - We need to verify that the resolver code does not
+// touch these implementation-specific methods.
+
+    public String getTargetName()
+    {
+        return m_targetName;
+    }
+
+    public VersionRange getTargetVersionRange()
+    {
+        return m_targetVersionRange;
+    }
+
+    public R4Directive[] getDirectives()
+    {
+        // TODO: RB - We should return copies of the arrays probably.
+        return m_directives;
+    }
+
+    public R4Attribute[] getAttributes()
+    {
+        // TODO: RB - We should return copies of the arrays probably.
+        return m_attributes;
+    }
+
+    public boolean isMultiple()
+    {
+        return false;
+    }
+
+    public boolean isOptional()
+    {
+        return m_isOptional;
+    }
+
+    public String getComment()
+    {
+        return "Comment for " + toString();
+    }
+
+    public boolean isSatisfied(Capability capability)
+    {
+        return capability.getNamespace().equals(getNamespace()) &&
+            doAttributesMatch((Capability) capability);
+    }
+
+    private boolean doAttributesMatch(Capability ec)
+    {
+        // Grab the capability's attributes.
+        R4Attribute[] capAttrs = ec.getAttributes();
+
+        // Cycle through all attributes of this import package
+        // and make sure its values match the attribute values
+        // of the specified export package.
+        for (int reqAttrIdx = 0; reqAttrIdx < m_attributes.length; reqAttrIdx++)
+        {
+            // Get current attribute from this import package.
+            R4Attribute reqAttr = m_attributes[reqAttrIdx];
+
+            // Check if the export package has the same attribute.
+            boolean found = false;
+            for (int capAttrIdx = 0;
+                (!found) && (capAttrIdx < capAttrs.length);
+                capAttrIdx++)
+            {
+                // Get current attribute for the export package.
+                R4Attribute capAttr = capAttrs[capAttrIdx];
+                // Check if the attribute names are equal.
+                if (reqAttr.getName().equals(capAttr.getName()))
+                {
+                    // We only recognize version types. If the value of the
+                    // attribute is a version/version range, then we use the
+                    // "in range" comparison, otherwise we simply use equals().
+                    if (capAttr.getValue() instanceof Version)
+                    {
+                        if (!((VersionRange) reqAttr.getValue()).isInRange((Version) capAttr.getValue()))
+                        {
+                            return false;
+                        }
+                    }
+                    else if (capAttr.getValue() instanceof Object[])
+                    {
+                        Object[] values = (Object[]) capAttr.getValue();
+                        boolean matched = false;
+                        for (int valIdx = 0; !matched && (valIdx < values.length); valIdx++)
+                        {
+                            if (reqAttr.getValue().equals(values[valIdx]))
+                            {
+                                matched = true;
+                            }
+                        }
+                        if (!matched)
+                        {
+                            return false;
+                        }
+                    }
+                    else if (!reqAttr.getValue().equals(capAttr.getValue()))
+                    {
+                        return false;
+                    }
+                    found = true;
+                }
+            }
+            // If the attribute was not found, then return false.
+            if (!found)
+            {
+                return false;
+            }
+        }
+
+        // Now, cycle through all attributes of the export package and verify that
+        // all mandatory attributes are present in this import package.
+        for (int capAttrIdx = 0; capAttrIdx < capAttrs.length; capAttrIdx++)
+        {
+            // Get current attribute for this package.
+            R4Attribute capAttr = capAttrs[capAttrIdx];
+
+            // If the export attribute is mandatory, then make sure
+            // this import package has the attribute.
+            if (capAttr.isMandatory())
+            {
+                boolean found = false;
+                for (int reqAttrIdx = 0;
+                    (!found) && (reqAttrIdx < m_attributes.length);
+                    reqAttrIdx++)
+                {
+                    // Get current attribute from specified package.
+                    R4Attribute reqAttr = m_attributes[reqAttrIdx];
+
+                    // Check if the attribute names are equal
+                    // and set found flag.
+                    if (capAttr.getName().equals(reqAttr.getName()))
+                    {
+                        found = true;
+                    }
+                }
+                // If not found, then return false.
+                if (!found)
+                {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+}
\ No newline at end of file