Modified manifest parser to generate generic capabilities and 
requirements for exports and imports, respectively. The generic 
capabilities/requirements are currently not used, but this is ongoing 
work for require-bundle support (FELIX-28).


git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@490526 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/Capability.java b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/Capability.java
index 05ccff1..6edbccb 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/Capability.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/Capability.java
@@ -18,19 +18,21 @@
  */
 package org.apache.felix.framework.util.manifestparser;
 
-import java.util.Collections;
-import java.util.Map;
+import java.util.*;
 import org.apache.felix.moduleloader.ICapability;
 
 public class Capability implements ICapability
 {
     private String m_namespace = null;
-    private Map m_propMap = null;
+    private R4Directive[] m_dirs = null;
+    private R4Attribute[] m_attrs = null;
+    private Map m_attrMap = null;
 
-    public Capability(String namespace, Map propMap)
+    public Capability(String namespace, R4Directive[] dirs, R4Attribute[] attrs)
     {
         m_namespace = namespace;
-        m_propMap = Collections.unmodifiableMap(propMap);
+        m_dirs = dirs;
+        m_attrs = attrs;
     }
 
     public String getNamespace()
@@ -38,13 +40,119 @@
         return m_namespace;
     }
 
-    public Map getProperties()
+    public R4Directive[] getDirectives()
     {
-        return m_propMap;
+        return m_dirs;
     }
 
-    public String[] getUses()
+    public Map getProperties()
     {
-        return null;
+        if (m_attrMap == null)
+        {
+            m_attrMap = new Map() {
+
+                public int size()
+                {
+                    return m_attrs.length;
+                }
+
+                public boolean isEmpty()
+                {
+                    return false;
+                }
+
+                public boolean containsKey(Object key)
+                {
+                    return (get(key) != null);
+                }
+
+                public boolean containsValue(Object value)
+                {
+                    for (int i = 0; i < m_attrs.length; i++)
+                    {
+                        if (m_attrs[i].getValue().equals(value))
+                        {
+                            return true;
+                        }
+                    }
+                    return false;
+                }
+
+                public Object get(Object key)
+                {
+                    for (int i = 0; i < m_attrs.length; i++)
+                    {
+                        if (m_attrs[i].getName().equals(key))
+                        {
+                            return m_attrs[i].getValue();
+                        }
+                    }
+                    return null;
+                }
+
+                public Object put(Object key, Object value)
+                {
+                    throw new UnsupportedOperationException("Map.put() not implemented.");
+                }
+
+                public Object remove(Object key)
+                {
+                    throw new UnsupportedOperationException("Map.remove() not implemented.");
+                }
+
+                public void putAll(Map t)
+                {
+                    throw new UnsupportedOperationException("Map.putAll() not implemented.");
+                }
+
+                public void clear()
+                {
+                    throw new UnsupportedOperationException("Map.clear() not implemented.");
+                }
+
+                public Set keySet()
+                {
+                    Set set = new HashSet();
+                    for (int i = 0; i < m_attrs.length; i++)
+                    {
+                        set.add(m_attrs[i].getName());
+                    }
+                    return set;
+                }
+
+                public Collection values()
+                {
+                    throw new java.lang.UnsupportedOperationException("Map.values() not implemented.");
+                }
+
+                public Set entrySet()
+                {
+                    throw new java.lang.UnsupportedOperationException("Map.entrySet() not implemented.");
+                }
+            };
+        }
+        return m_attrMap;
+    }
+
+// TODO: RB - Remove or simplify toString() for final version.
+    public String toString()
+    {
+        StringBuffer sb = new StringBuffer();
+        sb.append(m_namespace);
+        for (int i = 0; (m_dirs != null) && (i < m_dirs.length); i++)
+        {
+            sb.append(";");
+            sb.append(m_dirs[i].getName());
+            sb.append(":=");
+            sb.append(m_dirs[i].getValue());
+        }
+        for (int i = 0; (m_attrs != null) && (i < m_attrs.length); i++)
+        {
+            sb.append(";");
+            sb.append(m_attrs[i].getName());
+            sb.append("=");
+            sb.append(m_attrs[i].getValue());
+        }
+        return sb.toString();
     }
 }
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
index b47f7a7..3081466 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
@@ -24,6 +24,8 @@
 import org.apache.felix.framework.cache.BundleRevision;
 import org.apache.felix.framework.searchpolicy.*;
 import org.apache.felix.framework.util.*;
+import org.apache.felix.moduleloader.ICapability;
+import org.apache.felix.moduleloader.IRequirement;
 import org.osgi.framework.*;
 
 public class ManifestParser
@@ -33,6 +35,9 @@
     private Map m_headerMap = null;
     private String m_bundleSymbolicName = null;
     private Version m_bundleVersion = null;
+    private ICapability[] m_capabilities = null;
+    private IRequirement[] m_requirements = null;
+    private IRequirement[] m_dynamicReqs = null;
     private R4Export[] m_exports = null;
     private R4Import[] m_imports = null;
     private R4Import[] m_dynamics = null;
@@ -54,7 +59,16 @@
                 "Unknown 'Bundle-ManifestVersion' value: " + manifestVersion);
         }
 
-        // Verify bundle version syntax.
+        // 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();
+
+        //
+        // Get bundle version.
+        //
+
         if (m_headerMap.get(Constants.BUNDLE_VERSION) != null)
         {
             try
@@ -72,13 +86,6 @@
             }
         }
 
-        // Create map to check for duplicate imports/exports.
-        Map dupeMap = new HashMap();
-
-        //
-        // Get bundle version.
-        //
-
         //
         // Parse bundle symbolic name.
         //
@@ -100,10 +107,10 @@
                         + headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
             }
             m_bundleSymbolicName = (String) clauses[0][CLAUSE_PATHS_INDEX][0];
-//            Map propMap = new HashMap();
-//            propMap.put("symbolicname", m_bundleSymbolicName);
-//            propMap.put("version", m_bundleVersion);
-//            capList.add(new Capability(ICapability.MODULE_NAMESPACE, propMap));
+            R4Attribute[] attrs = new R4Attribute[2];
+            attrs[0] = new R4Attribute("symbolicname", m_bundleSymbolicName, false);
+            attrs[1] = new R4Attribute("version", m_bundleVersion, false);
+            capList.add(new Capability(ICapability.MODULE_NAMESPACE, null, attrs));
         }
 
         //
@@ -114,6 +121,15 @@
         m_exports = (R4Export[]) parseImportExportHeader(
             (String) headerMap.get(Constants.EXPORT_PACKAGE), true);
 
+        // Get export packages from bundle manifest.
+        ICapability[] exportCaps = parseExportHeader(
+            (String) headerMap.get(Constants.EXPORT_PACKAGE));
+//System.out.println("PARSED EXPORT CAPABILITIES:");
+//for (int capIdx = 0; capIdx < exportCaps.length; capIdx++)
+//{
+//    System.out.println(exportCaps[capIdx]);
+//}
+
         // Create non-duplicated export array.
         dupeMap.clear();
         for (int pkgIdx = 0; pkgIdx < m_exports.length; pkgIdx++)
@@ -174,6 +190,15 @@
         m_imports = (R4Import[]) parseImportExportHeader(
             (String) headerMap.get(Constants.IMPORT_PACKAGE), false);
 
+        // Get import packages from bundle manifest.
+        IRequirement[] importReqs = parseImportHeader(
+            (String) headerMap.get(Constants.IMPORT_PACKAGE));
+//System.out.println("PARSED IMPORT REQUIREMENTS:");
+//for (int reqIdx = 0; reqIdx < importReqs.length; reqIdx++)
+//{
+//    System.out.println(importReqs[reqIdx]);
+//}
+
         // Create non-duplicated import array.
         dupeMap.clear();
         for (int pkgIdx = 0; pkgIdx < m_imports.length; pkgIdx++)
@@ -205,6 +230,15 @@
         m_dynamics = (R4Import[]) parseImportExportHeader(
             (String) headerMap.get(Constants.DYNAMICIMPORT_PACKAGE), false);
 
+        // Get import packages from bundle manifest.
+        IRequirement[] dynReqs = parseImportHeader(
+            (String) headerMap.get(Constants.DYNAMICIMPORT_PACKAGE));
+//System.out.println("PARSED DYNAMIC IMPORT REQUIREMENTS:");
+//for (int reqIdx = 0; reqIdx < dynReqs.length; reqIdx++)
+//{
+//    System.out.println(dynReqs[reqIdx]);
+//}
+
         // Dynamic imports can have duplicates, so just check for import
         // of java.*.
         List dynList = new ArrayList();
@@ -682,6 +716,183 @@
         }
     }
 
+    public 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.");
+                }
+            }
+    
+            // 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++)
+            {
+                // 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,
+                    (String) 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()]);
+    }
+
+    public 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 appropriate 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 Version.
+            // Only imports can specify this attribue.
+            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++)
+            {
+                // 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,
+                    (String) 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()]);
+    }
+
     public static R4Package[] parseImportExportHeader(String header, boolean export)
     {
         Object[][][] clauses = parseStandardHeader(header);
diff --git a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/Requirement.java b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/Requirement.java
index 740b2b9..5933c49 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/Requirement.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/Requirement.java
@@ -20,6 +20,7 @@
 
 import org.apache.felix.framework.FilterImpl;
 import org.apache.felix.framework.util.MapToDictionary;
+import org.apache.felix.framework.util.VersionRange;
 import org.apache.felix.moduleloader.ICapability;
 import org.apache.felix.moduleloader.IRequirement;
 import org.osgi.framework.Filter;
@@ -28,12 +29,16 @@
 public class Requirement implements IRequirement
 {
     private String m_namespace = null;
+    private R4Directive[] m_dirs = null;
+    private R4Attribute[] m_attrs = null;
     private Filter m_filter = null;
 
-    public Requirement(String namespace, String filterStr) throws InvalidSyntaxException
+    public Requirement(String namespace, R4Directive[] dirs, R4Attribute[] attrs)
     {
         m_namespace = namespace;
-        m_filter = new FilterImpl(filterStr);
+        m_dirs = dirs;
+        m_attrs = attrs;
+        m_filter = convertToFilter();
     }
 
     public String getNamespace()
@@ -68,6 +73,83 @@
 
     public String toString()
     {
-        return m_filter.toString();
+        return getFilter().toString();
+    }
+
+    private Filter convertToFilter()
+    {
+        String filterStr = null;
+
+        StringBuffer sb = new StringBuffer("(&");
+
+        for (int i = 0; (m_attrs != null) && (i < m_attrs.length); i++)
+        {
+            // If this is a package import, then convert wild-carded
+            // dynamically imported package names to an OR comparison.
+            if (m_namespace.equals(ICapability.PACKAGE_NAMESPACE) &&
+                m_attrs[i].getName().equals(ICapability.PACKAGE_PROPERTY) &&
+                m_attrs[i].getValue().toString().endsWith(".*"))
+            {
+                int idx = m_attrs[i].getValue().toString().indexOf(".*");
+                sb.append("(|(package=");
+                sb.append(m_attrs[i].getValue().toString().substring(0, idx));
+                sb.append(")(package=");
+                sb.append(m_attrs[i].getValue().toString());
+                sb.append("))");
+            }
+            else if (m_attrs[i].getValue() instanceof VersionRange)
+            {
+                VersionRange vr = (VersionRange) m_attrs[i].getValue();
+                if (vr.isLowInclusive())
+                {
+                    sb.append("(version>=");
+                    sb.append(vr.getLow().toString());
+                    sb.append(")");
+                }
+                else
+                {
+                    sb.append("(!(version<=");
+                    sb.append(vr.getLow().toString());
+                    sb.append("))");
+                }
+
+                if (vr.getHigh() != null)
+                {
+                    if (vr.isHighInclusive())
+                    {
+                        sb.append("(version<=");
+                        sb.append(vr.getHigh().toString());
+                        sb.append(")");
+                    }
+                    else
+                    {
+                        sb.append("(!(version>=");
+                        sb.append(vr.getHigh().toString());
+                        sb.append("))");
+                    }
+                }
+            }
+            else
+            {
+                sb.append("(");
+                sb.append(m_attrs[i].getName());
+                sb.append("=");
+                sb.append(m_attrs[i].getValue().toString());
+                sb.append(")");
+            }
+        }
+
+        sb.append(")");
+
+        try
+        {
+            return new FilterImpl(sb.toString());
+        }
+        catch (InvalidSyntaxException ex)
+        {
+            // This should never happen, so we can safely ignore.
+        }
+
+        return null;
     }
 }
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/moduleloader/ICapability.java b/framework/src/main/java/org/apache/felix/moduleloader/ICapability.java
index bc271d1..935cd7b 100644
--- a/framework/src/main/java/org/apache/felix/moduleloader/ICapability.java
+++ b/framework/src/main/java/org/apache/felix/moduleloader/ICapability.java
@@ -30,5 +30,4 @@
 
     String getNamespace();
     Map getProperties();
-    String[] getUses();
 }
\ No newline at end of file