Applied patch (FELIX-26) from Arnaud Quiblier to improve the native library
selection algorithm. In doing so I realized that there were other issues
in our approach, namely that native library verfication was only being
performed at execution time but the spec requires some checking at
installation time. As a result, I moved a few things around and tried to
improve the overall structure in this area of the framework. It is a little
better now, but far from perfect. The changes do appear to have improved
our score with the TCK, but we are still missing some things that I will
document in the JIRA issue.


git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@439429 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/Felix.java b/framework/src/main/java/org/apache/felix/framework/Felix.java
index 0ed5465..1349530 100644
--- a/framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -17,8 +17,10 @@
 package org.apache.felix.framework;
 
 import java.io.*;
-import java.net.*;
-import java.security.*;
+import java.net.URL;
+import java.net.URLStreamHandler;
+import java.security.CodeSource;
+import java.security.ProtectionDomain;
 import java.util.*;
 
 import org.apache.felix.framework.cache.*;
@@ -2573,7 +2575,7 @@
     private IModule createModule(long targetId, int revision, Map headerMap)
         throws Exception
     {
-        ManifestParser mp = new ManifestParser(m_logger, headerMap);
+        ManifestParser mp = new ManifestParser(m_logger, m_config, headerMap);
 
         // Verify that the bundle symbolic name and version is unique.
         if (mp.getVersion().equals("2"))
@@ -2610,12 +2612,7 @@
             mp.getExports(),
             mp.getImports(),
             mp.getDynamicImports(),
-            mp.getLibraries(
-                m_cache,
-                targetId,
-                revision,
-                m_config.get(Constants.FRAMEWORK_OS_NAME),
-                m_config.get(Constants.FRAMEWORK_PROCESSOR)));
+            mp.getLibraries(m_cache.getArchive(targetId).getRevision(revision)));
 
         // Create the module using the module definition.
         IModule module = m_factory.createModule(
@@ -2828,13 +2825,9 @@
             System.getProperty("os.version"));
 
         String s = null;
-        s = R4Library.normalizePropertyValue(
-            FelixConstants.FRAMEWORK_OS_NAME,
-            System.getProperty("os.name"));
+        s = R4LibraryClause.normalizeOSName(System.getProperty("os.name"));
         m_configMutable.put(FelixConstants.FRAMEWORK_OS_NAME, s);
-        s = R4Library.normalizePropertyValue(
-            FelixConstants.FRAMEWORK_PROCESSOR,
-            System.getProperty("os.arch"));
+        s = R4LibraryClause.normalizeProcessor(System.getProperty("os.arch"));
         m_configMutable.put(FelixConstants.FRAMEWORK_PROCESSOR, s);
         m_configMutable.put(
             FelixConstants.FELIX_VERSION_PROPERTY, getFrameworkVersion());
diff --git a/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4Library.java b/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4Library.java
index 20d6cdd..89aa66c 100644
--- a/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4Library.java
+++ b/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4Library.java
@@ -17,142 +17,127 @@
 package org.apache.felix.framework.searchpolicy;
 
 import org.apache.felix.framework.Logger;
-import org.apache.felix.framework.cache.BundleCache;
+import org.apache.felix.framework.cache.BundleRevision;
+import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
 
 public class R4Library
 {
     private Logger m_logger = null;
-    private BundleCache m_cache = null;
-    private long m_bundleId = -1;
-    private int m_revision = -1;
-    private String m_os = null;
-    private String m_processor = null;
-    private R4LibraryHeader m_header = null;
+    private BundleRevision m_revision = null;
+    private String m_libraryFile = null;
+    private String[] m_osnames = null;
+    private String[] m_processors = null;
+    private String[] m_osversions = null;
+    private String[] m_languages = null;
+    private String m_selectionFilter = null;
 
-    public R4Library(
-        Logger logger, BundleCache cache, long bundleId, int revision,
-        String os, String processor, R4LibraryHeader header)
+    public R4Library(Logger logger, BundleRevision revision,
+        String libraryFile, String[] osnames, String[] processors, String[] osversions,
+        String[] languages, String selectionFilter)
     {
         m_logger = logger;
-        m_cache = cache;
-        m_bundleId = bundleId;
         m_revision = revision;
-        m_os = normalizePropertyValue(Constants.FRAMEWORK_OS_NAME, os);
-        m_processor = normalizePropertyValue(Constants.FRAMEWORK_PROCESSOR, processor);
-        m_header = header;
+        m_libraryFile = libraryFile;
+        m_osnames = osnames;
+        m_processors = processors;
+        m_osversions = osversions;
+        m_languages = languages;
+        m_selectionFilter = selectionFilter;
+    }
+
+    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>
      * Returns a file system path to the specified library.
      * </p>
+     * 
      * @param name the name of the library that is being requested.
      * @return a file system path to the specified library.
-    **/
+     */
     public String getPath(String name)
     {
-        if (m_header != null)
+        String libname = System.mapLibraryName(name);
+        if (m_libraryFile.indexOf(libname) >= 0)
         {
-            String libname = System.mapLibraryName(name);
-
-            // Check to see if the library matches.
-            boolean osOkay = checkOS(m_header.getOSNames());
-            boolean procOkay = checkProcessor(m_header.getProcessors());
-            if (m_header.getName().endsWith(libname) && osOkay && procOkay)
+            try
             {
-                try {
-                    return m_cache.getArchive(m_bundleId)
-                        .getRevision(m_revision).findLibrary(m_header.getName());
-                } catch (Exception ex) {
-                    m_logger.log(Logger.LOG_ERROR, "R4Library: Error finding library.", ex);
-                }
+                return m_revision.findLibrary(m_libraryFile);
+            }
+            catch (Exception ex)
+            {
+                m_logger.log(Logger.LOG_ERROR, "R4Library: Finding library '"
+                    + name + "'.", new BundleException(
+                    "Unable to find native library '" + name + "'."));
             }
         }
-
         return null;
     }
 
-    private boolean checkOS(String[] osnames)
+    public String toString()
     {
-        for (int i = 0; (osnames != null) && (i < osnames.length); i++)
+        if (m_libraryFile != null)
         {
-            String osname =
-                normalizePropertyValue(Constants.FRAMEWORK_OS_NAME, osnames[i]);
-            if (m_os.equals(osname))
+            StringBuffer sb = new StringBuffer();
+            sb.append(m_libraryFile);
+            sb.append(';');
+            for (int i = 0; (m_osnames != null) && (i < m_osnames.length); i++)
             {
-                return true;
+                sb.append(Constants.BUNDLE_NATIVECODE_OSNAME);
+                sb.append('=');
+                sb.append(m_osnames[i]);
+                sb.append(';');
             }
-        }
-        return false;
-    }
+            for (int i = 0; (m_processors != null) && (i < m_processors.length); i++)
+            {
+                sb.append(Constants.BUNDLE_NATIVECODE_PROCESSOR);
+                sb.append(m_processors[i]);
+                sb.append(';');
+            }
+            for (int i = 0; (m_osversions != null) && (i < m_osversions.length); i++)
+            {
+                sb.append(Constants.BUNDLE_NATIVECODE_OSVERSION);
+                sb.append(m_osversions[i]);
+                sb.append(';');
+            }
+            for (int i = 0; (m_languages != null) && (i < m_languages.length); i++)
+            {
+                sb.append(Constants.BUNDLE_NATIVECODE_LANGUAGE);
+                sb.append(m_languages[i]);
+                sb.append(';');
+            }
+            sb.append(Constants.SELECTION_FILTER_ATTRIBUTE);
+            sb.append('=');
+            sb.append('\'');
+            sb.append(m_selectionFilter);
+            sb.append('\'');
 
-    private boolean checkProcessor(String[] processors)
-    {
-        for (int i = 0; (processors != null) && (i < processors.length); i++)
-        {
-            String processor =
-                normalizePropertyValue(Constants.FRAMEWORK_PROCESSOR, processors[i]);
-            if (m_processor.equals(processor))
-            {
-                return true;
-            }
+            return sb.toString();
         }
-        return false;
-    }
-
-    /**
-     * This is simply a hack to try to create some standardized
-     * property values, since there seems to be many possible
-     * values for each JVM implementation.  Currently, this
-     * focuses on Windows and Linux and will certainly need
-     * to be changed in the future or at least edited.
-    **/
-    public static String normalizePropertyValue(String prop, String value)
-    {
-        prop = prop.toLowerCase();
-        value = value.toLowerCase();
-
-        if (prop.equals(Constants.FRAMEWORK_OS_NAME))
-        {
-            if (value.startsWith("linux"))
-            {
-                return "linux";
-            }
-            else if (value.startsWith("win"))
-            {
-                String os = "win";
-                if (value.indexOf("95") >= 0)
-                {
-                    os = "win95";
-                }
-                else if (value.indexOf("98") >= 0)
-                {
-                    os = "win98";
-                }
-                else if (value.indexOf("NT") >= 0)
-                {
-                    os = "winnt";
-                }
-                else if (value.indexOf("2000") >= 0)
-                {
-                    os = "win2000";
-                }
-                else if (value.indexOf("xp") >= 0)
-                {
-                    os = "winxp";
-                }
-                return os;
-            }
-        }
-        else if (prop.equals(Constants.FRAMEWORK_PROCESSOR))
-        {
-            if (value.endsWith("86"))
-            {
-                return "x86";
-            }
-        }
-
-        return value;
+        return "*";
     }
 }
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4LibraryClause.java b/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4LibraryClause.java
new file mode 100644
index 0000000..cbb2c50
--- /dev/null
+++ b/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4LibraryClause.java
@@ -0,0 +1,521 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.felix.framework.searchpolicy;
+
+import java.util.*;
+
+import org.apache.felix.framework.FilterImpl;
+import org.apache.felix.framework.Logger;
+import org.apache.felix.framework.util.FelixConstants;
+import org.apache.felix.framework.util.PropertyResolver;
+import org.osgi.framework.*;
+
+public class R4LibraryClause
+{
+    private String[] m_libraryFiles = null;
+    private String[] m_osnames = null;
+    private String[] m_processors = null;
+    private String[] m_osversions = null;
+    private String[] m_languages = null;
+    private String m_selectionFilter = null;
+
+    public R4LibraryClause(String[] libraryFiles, String[] osnames,
+        String[] processors, String[] osversions, String[] languages,
+        String selectionFilter)
+    {
+        m_libraryFiles = libraryFiles;
+        m_osnames = osnames;
+        m_processors = processors;
+        m_osversions = osversions;
+        m_languages = languages;
+        m_selectionFilter = selectionFilter;
+    }
+
+    public R4LibraryClause(R4LibraryClause library)
+    {
+        m_libraryFiles = library.m_libraryFiles;
+        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[] getLibraryFiles()
+    {
+        return m_libraryFiles;
+    }
+
+    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(PropertyResolver config) throws BundleException
+    {
+        String normal_osname = normalizeOSName(config.get(Constants.FRAMEWORK_OS_NAME));
+        String normal_processor = normalizeProcessor(config.get(Constants.FRAMEWORK_PROCESSOR));
+        String normal_osversion = normalizeOSVersion(config.get(Constants.FRAMEWORK_OS_VERSION));
+        String normal_language = config.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(config, getSelectionFilter()))
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean checkOSNames(String currentOSName, String[] osnames)
+    {
+        for (int i = 0; (osnames != null) && (i < osnames.length); i++)
+        {
+            if (osnames[i].equals(currentOSName))
+            {
+                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 BundleException
+    {
+        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 BundleException(
+                    "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(PropertyResolver config, String expr)
+        throws BundleException
+    {
+        // Get all framework properties
+        Dictionary dict = new Hashtable();
+        String[] keys = config.getKeys();
+        for (int i = 0; i < keys.length; i++)
+        {
+            dict.put(keys[i], config.get(keys[i]));
+        }
+        // Compute expression
+        try
+        {
+            FilterImpl filter = new FilterImpl(expr);
+            return filter.match(dict);
+        }
+        catch (Exception ex)
+        {
+            throw new BundleException(
+                "Error evaluating filter expression: " + expr, ex);
+        }
+    }
+
+    public static R4LibraryClause parse(Logger logger, String s)
+    {
+        try
+        {
+            if ((s == null) || (s.length() == 0))
+            {
+                return null;
+            }
+
+            if (s.equals(FelixConstants.BUNDLE_NATIVECODE_OPTIONAL))
+            {
+                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[] libFiles = 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.
+                    libFiles[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(value);
+                    }
+                    else if (property.equals(Constants.BUNDLE_NATIVECODE_OSVERSION))
+                    {
+                        osVersionList.add(value);
+                    }
+                    else if (property.equals(Constants.BUNDLE_NATIVECODE_PROCESSOR))
+                    {
+                        processorList.add(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[] actualLibFiles = new String[libCount];
+            System.arraycopy(libFiles, 0, actualLibFiles, 0, libCount);
+            return new R4LibraryClause(
+                actualLibFiles,
+                (String[]) osNameList.toArray(new String[0]),
+                (String[]) processorList.toArray(new String[0]),
+                (String[]) osVersionList.toArray(new String[0]),
+                (String[]) languageList.toArray(new String[0]),
+                selectionFilter);
+        }
+        catch (RuntimeException ex)
+        {
+            logger.log(Logger.LOG_ERROR,
+                "Error parsing native library header.", ex);
+            throw ex;
+        }
+    }
+
+    public static String normalizeOSName(String value)
+    {
+        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";
+            }
+            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"))
+        {
+            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)
+    {
+        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("x86-64") || value.startsWith("amd64"))
+        {
+            return "x86-64";
+        }
+        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
+        String VERSION_DELIM = ".";
+        String QUALIFIER_DELIM = "-";
+        int major = 0;
+        int minor = 0;
+        int micro = 0;
+        try
+        {
+            StringTokenizer st = new StringTokenizer(value, VERSION_DELIM, true);
+            major = Integer.parseInt(st.nextToken());
+
+            if (st.hasMoreTokens())
+            {
+                st.nextToken(); // consume delimiter
+                minor = Integer.parseInt(st.nextToken());
+
+                if (st.hasMoreTokens())
+                {
+                    st.nextToken(); // consume delimiter
+                    String microStr = st.nextToken();
+                    if (microStr.indexOf(QUALIFIER_DELIM) < 0)
+                    {
+                        micro = Integer.parseInt(microStr);
+                    }
+                    else
+                    {
+                        micro = Integer.parseInt(microStr.substring(0, microStr
+                            .indexOf(QUALIFIER_DELIM)));
+                    }
+                }
+            }
+        }
+        catch (Exception ex)
+        {
+            return Version.emptyVersion.toString();
+        }
+
+        return major + "." + minor + "." + micro;
+    }
+}
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4LibraryHeader.java b/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4LibraryHeader.java
deleted file mode 100644
index 28fde9b..0000000
--- a/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4LibraryHeader.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- *   Copyright 2006 The Apache Software Foundation
- *
- *   Licensed under the Apache License, Version 2.0 (the "License");
- *   you may not use this file except in compliance with the License.
- *   You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing, software
- *   distributed under the License is distributed on an "AS IS" BASIS,
- *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *   See the License for the specific language governing permissions and
- *   limitations under the License.
- *
- */
-package org.apache.felix.framework.searchpolicy;
-
-import java.util.*;
-
-import org.apache.felix.framework.Logger;
-import org.osgi.framework.Constants;
-
-public class R4LibraryHeader
-{
-    private String m_name = null;
-    private String[] m_osnames = null;
-    private String[] m_osversions = null;
-    private String[] m_processors = null;
-    private String[] m_languages = null;
-
-    public R4LibraryHeader(String name, String[] osnames, String[] osversions,
-        String[] processors, String[] languages)
-    {
-        m_name = name;
-        m_osnames = osnames;
-        m_osversions = osversions;
-        m_processors = processors;
-        m_languages = languages;
-    }
-
-    public R4LibraryHeader(R4LibraryHeader library)
-    {
-        m_name = library.m_name;
-        m_osnames = library.m_osnames;
-        m_osversions = library.m_osversions;
-        m_processors = library.m_processors;
-        m_languages = library.m_languages;
-    }
-
-    public String getName()
-    {
-        return m_name;
-    }
-
-    public String[] getOSNames()
-    {
-        return m_osnames;
-    }
-
-    public String[] getOSVersions()
-    {
-        return m_osversions;
-    }
-
-    public String[] getProcessors()
-    {
-        return m_processors;
-    }
-
-    public static R4LibraryHeader[] parse(Logger logger, String s)
-    {
-        try
-        {
-            if ((s == null) || (s.length() == 0))
-            {
-                return null;
-            }
-
-            // The tokens are separated by semicolons and may include
-            // any number of libraries (whose name starts with a "/")
-            // along with one set of associated properties.
-            StringTokenizer st = new StringTokenizer(s, ";");
-            String[] libs = new String[st.countTokens()];
-            List osNameList = new ArrayList();
-            List osVersionList = new ArrayList();
-            List processorList = new ArrayList();
-            List languageList = new ArrayList();
-            int libCount = 0;
-            while (st.hasMoreTokens())
-            {
-                String token = st.nextToken().trim();
-                if (token.indexOf('=') < 0)
-                {
-                    // Remove the slash, if necessary.
-                    libs[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.
-                    StringTokenizer stProp = new StringTokenizer(token, "=");
-                    if (stProp.countTokens() != 2)
-                    {
-                        throw new IllegalArgumentException(
-                            "Bundle manifest native library entry malformed: " + token);
-                    }
-                    String property = stProp.nextToken().trim().toLowerCase();
-                    String value = stProp.nextToken().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(value);
-                    }
-                    else if (property.equals(Constants.BUNDLE_NATIVECODE_OSVERSION))
-                    {
-                        osVersionList.add(value);
-                    }
-                    else if (property.equals(Constants.BUNDLE_NATIVECODE_PROCESSOR))
-                    {
-                        processorList.add(value);
-                    }
-                    else if (property.equals(Constants.BUNDLE_NATIVECODE_LANGUAGE))
-                    {
-                        languageList.add(value);
-                    }
-                }
-            }
-
-            if (libCount == 0)
-            {
-                return null;
-            }
-
-            R4LibraryHeader[] libraries = new R4LibraryHeader[libCount];
-            for (int i = 0; i < libCount; i++)
-            {
-                libraries[i] =
-                    new R4LibraryHeader(
-                        libs[i],
-                        (String[]) osNameList.toArray(new String[0]),
-                        (String[]) osVersionList.toArray(new String[0]),
-                        (String[]) processorList.toArray(new String[0]),
-                        (String[]) languageList.toArray(new String[0]));
-            }
-
-            return libraries;
-
-        }
-        catch (RuntimeException ex)
-        {
-            logger.log(
-                Logger.LOG_ERROR,
-                "Error parsing native library header.",
-                ex);
-            throw ex;
-        }
-    }
-
-    public String toString()
-    {
-        return m_name;
-    }
-}
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4SearchPolicyCore.java b/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4SearchPolicyCore.java
index fcee6be..904ab71 100755
--- a/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4SearchPolicyCore.java
+++ b/framework/src/main/java/org/apache/felix/framework/searchpolicy/R4SearchPolicyCore.java
@@ -23,9 +23,7 @@
 import org.apache.felix.framework.Logger;
 import org.apache.felix.framework.util.*;
 import org.apache.felix.moduleloader.*;
-import org.osgi.framework.Constants;
-import org.osgi.framework.PackagePermission;
-import org.osgi.framework.Version;
+import org.osgi.framework.*;
 
 public class R4SearchPolicyCore implements ModuleListener
 {
@@ -505,18 +503,15 @@
             name = name.substring(1);
         }
 
-        // TODO: This "matching" algorithm does not fully
-        // match the spec and should be improved.
         R4Library[] libs = module.getDefinition().getLibraries();
         for (int i = 0; (libs != null) && (i < libs.length); i++)
         {
-            String path = libs[i].getPath(name);
-            if (path != null)
+            String lib = libs[i].getPath(name);
+            if (lib != null)
             {
-                return path;
+                return lib;
             }
         }
-
         return null;
     }
 
diff --git a/framework/src/main/java/org/apache/felix/framework/util/FelixConstants.java b/framework/src/main/java/org/apache/felix/framework/util/FelixConstants.java
index 2ebc9c8..d71cb23 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/FelixConstants.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/FelixConstants.java
@@ -33,6 +33,7 @@
     public static final String PACKAGE_SEPARATOR = ";";
     public static final String VERSION_SEGMENT_SEPARATOR = ".";
     public static final int VERSION_SEGMENT_COUNT = 3;
+    public static final String BUNDLE_NATIVECODE_OPTIONAL = "*";
 
     // Miscellaneous OSGi constants.
     public static final String BUNDLE_URL_PROTOCOL = "bundle";
@@ -56,4 +57,4 @@
 
     // Miscellaneous properties values.
     public static final String FAKE_URL_PROTOCOL_VALUE = "location:";
-}
+}
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/util/ManifestParser.java b/framework/src/main/java/org/apache/felix/framework/util/ManifestParser.java
index d656770..91c9bb2 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/ManifestParser.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/ManifestParser.java
@@ -16,26 +16,29 @@
  */
 package org.apache.felix.framework.util;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 import org.apache.felix.framework.Logger;
-import org.apache.felix.framework.cache.BundleCache;
+import org.apache.felix.framework.cache.BundleRevision;
 import org.apache.felix.framework.searchpolicy.*;
 import org.osgi.framework.*;
 
 public class ManifestParser
 {
     private Logger m_logger = null;
+    private PropertyResolver m_config = null;
     private Map m_headerMap = null;
     private R4Export[] m_exports = null;
     private R4Import[] m_imports = null;
     private R4Import[] m_dynamics = null;
-    private R4LibraryHeader[] m_libraryHeaders = null;
+    private R4LibraryClause[] m_libraryHeaders = null;
+    private boolean m_libraryHeadersOptional = false;
 
-    public ManifestParser(Logger logger, Map headerMap) throws BundleException
+    public ManifestParser(Logger logger, PropertyResolver config, Map headerMap)
+        throws BundleException
     {
         m_logger = logger;
+        m_config = config;
         m_headerMap = headerMap;
 
         // Verify that only manifest version 2 is specified.
@@ -152,6 +155,17 @@
                 m_logger,
                 Util.parseDelimitedString(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].getLibraryFiles() == 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;
+        }
+
         // Do final checks and normalization of manifest.
         if (getVersion().equals("2"))
         {
@@ -189,21 +203,183 @@
         return m_dynamics;
     }
 
-    public R4LibraryHeader[] getLibraryHeaders()
+    public R4LibraryClause[] getLibraryClauses()
     {
         return m_libraryHeaders;
     }
 
-    public R4Library[] getLibraries(
-        BundleCache cache, long id, int revision, String osName, String processor)
+    /**
+     * <p>
+     * This method returns the selected native library metadata from
+     * the manifest. The information is not the raw metadata from the
+     * manifest, but is native library metadata clause selected according
+     * to the OSGi native library clause selection policy. The metadata
+     * returned by this method will be attached directly to a module and
+     * used for finding its native libraries at run time. To inspect the
+     * raw native library metadata refer to <tt>getLibraryClauses()</tt>.
+     * </p>
+     * @param revision the bundle revision for the module.
+     * @return an array of selected library metadata objects from the manifest.
+     * @throws BundleException if any problems arise.
+     */
+    public R4Library[] getLibraries(BundleRevision revision) throws BundleException
     {
-        R4Library[] libraries = new R4Library[m_libraryHeaders.length];
-        for (int i = 0; i < libraries.length; i++)
+        R4LibraryClause clause = getSelectedLibraryClause();
+
+        if (clause != null)
         {
-            libraries[i] = new R4Library(
-                m_logger, cache, id, revision, osName, processor, m_libraryHeaders[i]);
+            R4Library[] libraries = new R4Library[clause.getLibraryFiles().length];
+            for (int i = 0; i < libraries.length; i++)
+            {
+                libraries[i] = new R4Library(
+                    m_logger, revision, clause.getLibraryFiles()[i],
+                    clause.getOSNames(), clause.getProcessors(), clause.getOSVersions(),
+                    clause.getLanguages(), clause.getSelectionFilter());
+            }
+            return libraries;
         }
-        return libraries;
+        return null;
+    }
+
+    private R4LibraryClause getSelectedLibraryClause() throws BundleException
+    {
+        if ((m_libraryHeaders != null) && (m_libraryHeaders.length > 0))
+        {
+            List clauseList = new ArrayList();
+
+            // Search for matching native clauses.
+            for (int i = 0; i < m_libraryHeaders.length; i++)
+            {
+                if (m_libraryHeaders[i].match(m_config))
+                {
+                    clauseList.add(m_libraryHeaders[i]);
+                }
+            }
+
+            // Select the matching native clause.
+            int selected = 0;
+            if (clauseList.size() == 0)
+            {
+                // If optional clause exists, no error thrown.
+                if (m_libraryHeadersOptional)
+                {
+                    return null;
+                }
+                else
+                {
+                    throw new BundleException("Unable to select a native library clause.");
+                }
+            }
+            else if (clauseList.size() == 1)
+            {
+                selected = 0;
+            }
+            else if (clauseList.size() > 1)
+            {
+                selected = firstSortedClause(clauseList);
+            }
+            return ((R4LibraryClause) clauseList.get(selected));
+        }
+
+        return null;
+    }
+
+    private int firstSortedClause(List clauseList)
+    {
+        ArrayList indexList = new ArrayList();
+        ArrayList selection = new ArrayList();
+
+        // Init index list
+        for (int i = 0; i < clauseList.size(); i++)
+        {
+            indexList.add("" + i);
+        }
+
+        // Select clause with 'osversion' range declared
+        // and get back the max floor of 'osversion' ranges.
+        Version osVersionRangeMaxFloor = new Version(0, 0, 0);
+        for (int i = 0; i < indexList.size(); i++)
+        {
+            int index = Integer.parseInt(indexList.get(i).toString());
+            String[] osversions = ((R4LibraryClause) clauseList.get(index)).getOSVersions();
+            if (osversions != null)
+            {
+                selection.add("" + indexList.get(i));
+            }
+            for (int k = 0; (osversions != null) && (k < osversions.length); k++)
+            {
+                VersionRange range = VersionRange.parse(osversions[k]);
+                if ((range.getLow()).compareTo(osVersionRangeMaxFloor) >= 0)
+                {
+                    osVersionRangeMaxFloor = range.getLow();
+                }
+            }
+        }
+
+        if (selection.size() == 1)
+        {
+            return Integer.parseInt(selection.get(0).toString());
+        }
+        else if (selection.size() > 1)
+        {
+            // Keep only selected clauses with an 'osversion'
+            // equal to the max floor of 'osversion' ranges.
+            indexList = selection;
+            selection = new ArrayList();
+            for (int i = 0; i < indexList.size(); i++)
+            {
+                int index = Integer.parseInt(indexList.get(i).toString());
+                String[] osversions = ((R4LibraryClause) clauseList.get(index)).getOSVersions();
+                for (int k = 0; k < osversions.length; k++)
+                {
+                    VersionRange range = VersionRange.parse(osversions[k]);
+                    if ((range.getLow()).compareTo(osVersionRangeMaxFloor) >= 0)
+                    {
+                        selection.add("" + indexList.get(i));
+                    }
+                }
+            }
+        }
+
+        if (selection.size() == 0)
+        {
+            // Re-init index list.
+            selection.clear();
+            indexList.clear();
+            for (int i = 0; i < clauseList.size(); i++)
+            {
+                indexList.add("" + i);
+            }
+        }
+        else if (selection.size() == 1)
+        {
+            return Integer.parseInt(selection.get(0).toString());
+        }
+        else
+        {
+            indexList = selection;
+            selection.clear();
+        }
+
+        // Keep only clauses with 'language' declared.
+        for (int i = 0; i < indexList.size(); i++)
+        {
+            int index = Integer.parseInt(indexList.get(i).toString());
+            if (((R4LibraryClause) clauseList.get(index)).getLanguages() != null)
+            {
+                selection.add("" + indexList.get(i));
+            }
+        }
+
+        // Return the first sorted clause
+        if (selection.size() == 0)
+        {
+            return 0;
+        }
+        else
+        {
+            return Integer.parseInt(selection.get(0).toString());
+        }
     }
 
     private void checkAndNormalizeR3() throws BundleException
diff --git a/framework/src/main/java/org/apache/felix/framework/util/Util.java b/framework/src/main/java/org/apache/felix/framework/util/Util.java
index 7f443bc..4d840b5 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/Util.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/Util.java
@@ -303,28 +303,23 @@
      * @return an array of <tt>LibraryInfo</tt> objects for the
      *         passed in strings.
     **/
-    public static R4LibraryHeader[] parseLibraryStrings(Logger logger, String[] libStrs)
+    public static R4LibraryClause[] parseLibraryStrings(Logger logger, String[] libStrs)
         throws IllegalArgumentException
     {
         if (libStrs == null)
         {
-            return new R4LibraryHeader[0];
+            return new R4LibraryClause[0];
         }
 
         List libList = new ArrayList();
 
         for (int i = 0; i < libStrs.length; i++)
         {
-            R4LibraryHeader[] libs = R4LibraryHeader.parse(logger, libStrs[i]);
-            for (int libIdx = 0;
-                (libs != null) && (libIdx < libs.length);
-                libIdx++)
-            {
-                libList.add(libs[libIdx]);
-            }
+            R4LibraryClause clause = R4LibraryClause.parse(logger, libStrs[i]);
+            libList.add(clause);
         }
 
-        return (R4LibraryHeader[]) libList.toArray(new R4LibraryHeader[libList.size()]);
+        return (R4LibraryClause[]) libList.toArray(new R4LibraryClause[libList.size()]);
     }
 
     private static final byte encTab[] = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,