ace-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ma...@apache.org
Subject svn commit: r1464402 [10/11] - in /ace/trunk: org.apache.ace.deployment.api/ org.apache.ace.deployment.deploymentadmin/ org.apache.ace.deployment.itest/ org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/ org.apache.ace.deployment.provide...
Date Thu, 04 Apr 2013 09:43:37 GMT
Added: ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/VersionRange.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/VersionRange.java?rev=1464402&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/VersionRange.java (added)
+++ ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/VersionRange.java Thu Apr  4 09:43:34 2013
@@ -0,0 +1,159 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework.util;
+
+import org.osgi.framework.Version;
+
+public class VersionRange
+{
+    private final Version m_floor;
+    private final boolean m_isFloorInclusive;
+    private final Version m_ceiling;
+    private final boolean m_isCeilingInclusive;
+    public static final VersionRange infiniteRange
+        = new VersionRange(Version.emptyVersion, true, null, true);
+
+    public VersionRange(
+        Version low, boolean isLowInclusive,
+        Version high, boolean isHighInclusive)
+    {
+        m_floor = low;
+        m_isFloorInclusive = isLowInclusive;
+        m_ceiling = high;
+        m_isCeilingInclusive = isHighInclusive;
+    }
+
+    public Version getFloor()
+    {
+        return m_floor;
+    }
+
+    public boolean isFloorInclusive()
+    {
+        return m_isFloorInclusive;
+    }
+
+    public Version getCeiling()
+    {
+        return m_ceiling;
+    }
+
+    public boolean isCeilingInclusive()
+    {
+        return m_isCeilingInclusive;
+    }
+
+    public boolean isInRange(Version version)
+    {
+        // We might not have an upper end to the range.
+        if (m_ceiling == null)
+        {
+            return (version.compareTo(m_floor) >= 0);
+        }
+        else if (isFloorInclusive() && isCeilingInclusive())
+        {
+            return (version.compareTo(m_floor) >= 0) && (version.compareTo(m_ceiling) <= 0);
+        }
+        else if (isCeilingInclusive())
+        {
+            return (version.compareTo(m_floor) > 0) && (version.compareTo(m_ceiling) <= 0);
+        }
+        else if (isFloorInclusive())
+        {
+            return (version.compareTo(m_floor) >= 0) && (version.compareTo(m_ceiling) < 0);
+        }
+        return (version.compareTo(m_floor) > 0) && (version.compareTo(m_ceiling) < 0);
+    }
+
+    public static VersionRange parse(String range)
+    {
+        // Check if the version is an interval.
+        if (range.indexOf(',') >= 0)
+        {
+            String s = range.substring(1, range.length() - 1);
+            String vlo = s.substring(0, s.indexOf(',')).trim();
+            String vhi = s.substring(s.indexOf(',') + 1, s.length()).trim();
+            return new VersionRange (
+                new Version(vlo), (range.charAt(0) == '['),
+                new Version(vhi), (range.charAt(range.length() - 1) == ']'));
+        }
+        else
+        {
+            return new VersionRange(new Version(range), true, null, false);
+        }
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        final VersionRange other = (VersionRange) obj;
+        if (m_floor != other.m_floor && (m_floor == null || !m_floor.equals(other.m_floor)))
+        {
+            return false;
+        }
+        if (m_isFloorInclusive != other.m_isFloorInclusive)
+        {
+            return false;
+        }
+        if (m_ceiling != other.m_ceiling && (m_ceiling == null || !m_ceiling.equals(other.m_ceiling)))
+        {
+            return false;
+        }
+        if (m_isCeilingInclusive != other.m_isCeilingInclusive)
+        {
+            return false;
+        }
+        return true;
+    }
+
+    public int hashCode()
+    {
+        int hash = 5;
+        hash = 97 * hash + (m_floor != null ? m_floor.hashCode() : 0);
+        hash = 97 * hash + (m_isFloorInclusive ? 1 : 0);
+        hash = 97 * hash + (m_ceiling != null ? m_ceiling.hashCode() : 0);
+        hash = 97 * hash + (m_isCeilingInclusive ? 1 : 0);
+        return hash;
+    }
+
+    public String toString()
+    {
+        if (m_ceiling != null)
+        {
+            StringBuffer sb = new StringBuffer();
+            sb.append(m_isFloorInclusive ? '[' : '(');
+            sb.append(m_floor.toString());
+            sb.append(',');
+            sb.append(m_ceiling.toString());
+            sb.append(m_isCeilingInclusive ? ']' : ')');
+            return sb.toString();
+        }
+        else
+        {
+            return m_floor.toString();
+        }
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/manifestparser/ManifestParser.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/manifestparser/ManifestParser.java?rev=1464402&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/manifestparser/ManifestParser.java (added)
+++ ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/manifestparser/ManifestParser.java Thu Apr  4 09:43:34 2013
@@ -0,0 +1,1977 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework.util.manifestparser;
+
+import java.util.*;
+import java.util.ArrayList;
+import java.util.Map.Entry;
+
+import org.apache.felix.framework.Logger;
+import org.apache.felix.framework.capabilityset.SimpleFilter;
+import org.apache.felix.framework.wiring.BundleCapabilityImpl;
+import org.apache.felix.framework.util.FelixConstants;
+import org.apache.felix.framework.util.VersionRange;
+import org.apache.felix.framework.wiring.BundleRequirementImpl;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+
+public class ManifestParser
+{
+    private final Logger m_logger;
+    private final Map m_configMap;
+    private final Map m_headerMap;
+    private volatile int m_activationPolicy = FelixConstants.EAGER_ACTIVATION;
+    private volatile String m_activationIncludeDir;
+    private volatile String m_activationExcludeDir;
+    private volatile boolean m_isExtension = false;
+    private volatile String m_bundleSymbolicName;
+    private volatile Version m_bundleVersion;
+    private volatile List<BundleCapability> m_capabilities;
+    private volatile List<BundleRequirement> m_requirements;
+    private volatile List<R4LibraryClause> m_libraryClauses;
+    private volatile boolean m_libraryHeadersOptional = false;
+
+    public ManifestParser(Logger logger, Map configMap, BundleRevision owner, Map headerMap)
+        throws BundleException
+    {
+        m_logger = logger;
+        m_configMap = configMap;
+        m_headerMap = headerMap;
+
+        // Verify that only manifest version 2 is specified.
+        String manifestVersion = getManifestVersion(m_headerMap);
+        if ((manifestVersion != null) && !manifestVersion.equals("2"))
+        {
+            throw new BundleException(
+                "Unknown 'Bundle-ManifestVersion' value: " + manifestVersion);
+        }
+
+        // Create lists to hold capabilities and requirements.
+        List<BundleCapabilityImpl> capList = new ArrayList();
+
+        //
+        // Parse bundle version.
+        //
+
+        m_bundleVersion = Version.emptyVersion;
+        if (headerMap.get(Constants.BUNDLE_VERSION) != null)
+        {
+            try
+            {
+                m_bundleVersion = Version.parseVersion(
+                    (String) headerMap.get(Constants.BUNDLE_VERSION));
+            }
+            catch (RuntimeException ex)
+            {
+                // R4 bundle versions must parse, R3 bundle version may not.
+                if (getManifestVersion().equals("2"))
+                {
+                    throw ex;
+                }
+                m_bundleVersion = Version.emptyVersion;
+            }
+        }
+
+        //
+        // Parse bundle symbolic name.
+        //
+
+        BundleCapabilityImpl bundleCap = parseBundleSymbolicName(owner, m_headerMap);
+        if (bundleCap != null)
+        {
+            m_bundleSymbolicName = (String)
+                bundleCap.getAttributes().get(BundleRevision.BUNDLE_NAMESPACE);
+
+            // Add a bundle capability and a host capability to all
+            // non-fragment bundles. A host capability is the same
+            // as a require capability, but with a different capability
+            // namespace. Bundle capabilities resolve required-bundle
+            // dependencies, while host capabilities resolve fragment-host
+            // dependencies.
+            if (headerMap.get(Constants.FRAGMENT_HOST) == null)
+            {
+                // All non-fragment bundles have host capabilities.
+                capList.add(bundleCap);
+                // A non-fragment bundle can choose to not have a host capability.
+                String attachment =
+                    bundleCap.getDirectives().get(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE);
+                attachment = (attachment == null)
+                    ? Constants.FRAGMENT_ATTACHMENT_RESOLVETIME
+                    : attachment;
+                if (!attachment.equalsIgnoreCase(Constants.FRAGMENT_ATTACHMENT_NEVER))
+                {
+                    Map<String, Object> hostAttrs =
+                        new HashMap<String, Object>(bundleCap.getAttributes());
+                    Object value = hostAttrs.remove(BundleRevision.BUNDLE_NAMESPACE);
+                    hostAttrs.put(BundleRevision.HOST_NAMESPACE, value);
+                    capList.add(new BundleCapabilityImpl(
+                        owner, BundleRevision.HOST_NAMESPACE,
+                        bundleCap.getDirectives(),
+                        hostAttrs));
+                }
+            }
+
+            // Add a singleton capability if the bundle is a singleton.
+            // This is sort of a hack, but we need this for the resolver
+            // to be able to resolve singletons. It is not possible to
+            // attach this information to the bundle or host capabilities
+            // because fragments don't have those capabilities, but fragments
+            // can be singletons too.
+// TODO: OSGi R4.4 - Eventually we will have an identity capability from OBR
+//       that we can use instead of this custom singleton capability.
+            if (isSingleton(bundleCap))
+            {
+                Map<String, Object> singletonAttrs =
+                    new HashMap<String, Object>(bundleCap.getAttributes());
+                Object value = singletonAttrs.remove(BundleRevision.BUNDLE_NAMESPACE);
+                singletonAttrs.put(BundleCapabilityImpl.SINGLETON_NAMESPACE, value);
+                capList.add(new BundleCapabilityImpl(
+                    owner, BundleCapabilityImpl.SINGLETON_NAMESPACE,
+                    Collections.EMPTY_MAP,
+                    singletonAttrs));
+            }
+        }
+
+        // Verify that bundle symbolic name is specified.
+        if (getManifestVersion().equals("2") && (m_bundleSymbolicName == null))
+        {
+            throw new BundleException(
+                "R4 bundle manifests must include bundle symbolic name.");
+        }
+
+        //
+        // Parse Fragment-Host.
+        //
+
+        List<BundleRequirementImpl> hostReqs = parseFragmentHost(m_logger, owner, m_headerMap);
+
+        //
+        // Parse Require-Bundle
+        //
+
+        List<ParsedHeaderClause> rbClauses =
+            parseStandardHeader((String) headerMap.get(Constants.REQUIRE_BUNDLE));
+        rbClauses = normalizeRequireClauses(m_logger, rbClauses, getManifestVersion());
+        List<BundleRequirementImpl> rbReqs = convertRequires(rbClauses, owner);
+
+        //
+        // Parse Import-Package.
+        //
+
+        List<ParsedHeaderClause> importClauses =
+            parseStandardHeader((String) headerMap.get(Constants.IMPORT_PACKAGE));
+        importClauses = normalizeImportClauses(m_logger, importClauses, getManifestVersion());
+        List<BundleRequirement> importReqs = convertImports(importClauses, owner);
+
+        //
+        // Parse DynamicImport-Package.
+        //
+
+        List<ParsedHeaderClause> dynamicClauses =
+            parseStandardHeader((String) headerMap.get(Constants.DYNAMICIMPORT_PACKAGE));
+        dynamicClauses = normalizeDynamicImportClauses(m_logger, dynamicClauses, getManifestVersion());
+        List<BundleRequirement> dynamicReqs = convertImports(dynamicClauses, owner);
+
+        //
+        // Parse Require-Capability.
+        //
+
+        List<ParsedHeaderClause> requireClauses =
+            parseStandardHeader((String) headerMap.get(Constants.REQUIRE_CAPABILITY));
+        importClauses = normalizeRequireCapabilityClauses(
+            m_logger, requireClauses, getManifestVersion());
+        List<BundleRequirement> requireReqs = convertRequireCapabilities(importClauses, owner);
+
+        //
+        // Parse Export-Package.
+        //
+
+        List<ParsedHeaderClause> exportClauses =
+            parseStandardHeader((String) headerMap.get(Constants.EXPORT_PACKAGE));
+        exportClauses = normalizeExportClauses(logger, exportClauses,
+            getManifestVersion(), m_bundleSymbolicName, m_bundleVersion);
+        List<BundleCapability> exportCaps = convertExports(exportClauses, owner);
+
+        //
+        // Parse Provide-Capability.
+        //
+
+        List<ParsedHeaderClause> provideClauses =
+            parseStandardHeader((String) headerMap.get(Constants.PROVIDE_CAPABILITY));
+        exportClauses = normalizeProvideCapabilityClauses(
+            logger, provideClauses, getManifestVersion());
+        List<BundleCapability> provideCaps = convertProvideCapabilities(provideClauses, owner);
+
+        //
+        // Calculate implicit imports.
+        //
+
+        if (!getManifestVersion().equals("2"))
+        {
+            List<ParsedHeaderClause> implicitClauses =
+                calculateImplicitImports(exportCaps, importClauses);
+            importReqs.addAll(convertImports(implicitClauses, owner));
+
+            List<ParsedHeaderClause> allImportClauses =
+                new ArrayList<ParsedHeaderClause>(implicitClauses.size() + importClauses.size());
+            allImportClauses.addAll(importClauses);
+            allImportClauses.addAll(implicitClauses);
+
+            exportCaps = calculateImplicitUses(exportCaps, allImportClauses);
+        }
+
+        // Combine all capabilities.
+        m_capabilities = new ArrayList(
+             capList.size() + exportCaps.size() + provideCaps.size());
+        m_capabilities.addAll(capList);
+        m_capabilities.addAll(exportCaps);
+        m_capabilities.addAll(provideCaps);
+
+        // Combine all requirements.
+        m_requirements = new ArrayList(
+            importReqs.size() + rbReqs.size() + hostReqs.size()
+            + requireReqs.size() + dynamicReqs.size());
+        m_requirements.addAll(importReqs);
+        m_requirements.addAll(rbReqs);
+        m_requirements.addAll(hostReqs);
+        m_requirements.addAll(requireReqs);
+        m_requirements.addAll(dynamicReqs);
+
+        //
+        // Parse Bundle-NativeCode.
+        //
+
+        // Parse native library clauses.
+        m_libraryClauses =
+            parseLibraryStrings(
+                m_logger,
+                parseDelimitedString((String) m_headerMap.get(Constants.BUNDLE_NATIVECODE), ","));
+
+        // Check to see if there was an optional native library clause, which is
+        // represented by a null library header; if so, record it and remove it.
+        if (!m_libraryClauses.isEmpty() &&
+            (m_libraryClauses.get(m_libraryClauses.size() - 1).getLibraryEntries() == null))
+        {
+            m_libraryHeadersOptional = true;
+            m_libraryClauses.remove(m_libraryClauses.size() - 1);
+        }
+
+        //
+        // Parse activation policy.
+        //
+
+        // This sets m_activationPolicy, m_includedPolicyClasses, and
+        // m_excludedPolicyClasses.
+        parseActivationPolicy(headerMap);
+
+        m_isExtension = checkExtensionBundle(headerMap);
+    }
+
+    private static boolean isSingleton(BundleCapabilityImpl cap)
+    {
+        if (cap.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE))
+        {
+            String value = cap.getDirectives().get(Constants.SINGLETON_DIRECTIVE);
+            if ((value != null) && Boolean.valueOf(value))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static List<ParsedHeaderClause> normalizeImportClauses(
+        Logger logger, List<ParsedHeaderClause> clauses, String mv)
+        throws BundleException
+    {
+        // Verify that the values are equals if the package specifies
+        // both version and specification-version attributes.
+        Set dupeSet = new HashSet();
+        for (ParsedHeaderClause clause : clauses)
+        {
+            // Check for "version" and "specification-version" attributes
+            // and verify they are the same if both are specified.
+            Object v = clause.m_attrs.get(Constants.VERSION_ATTRIBUTE);
+            Object sv = clause.m_attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION);
+            if ((v != null) && (sv != null))
+            {
+                // Verify they are equal.
+                if (!((String) v).trim().equals(((String) sv).trim()))
+                {
+                    throw new IllegalArgumentException(
+                        "Both version and specification-version are specified, but they are not equal.");
+                }
+            }
+
+            // Ensure that only the "version" attribute is used and convert
+            // it to the VersionRange type.
+            if ((v != null) || (sv != null))
+            {
+                clause.m_attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+                v = (v == null) ? sv : v;
+                clause.m_attrs.put(
+                    Constants.VERSION_ATTRIBUTE,
+                    VersionRange.parse(v.toString()));
+            }
+
+            // If bundle version is specified, then convert its type to VersionRange.
+            v = clause.m_attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+            if (v != null)
+            {
+                clause.m_attrs.put(
+                    Constants.BUNDLE_VERSION_ATTRIBUTE,
+                    VersionRange.parse(v.toString()));
+            }
+
+            // Verify java.* is not imported, nor any duplicate imports.
+            for (String pkgName : clause.m_paths)
+            {
+                if (!dupeSet.contains(pkgName))
+                {
+                    // Verify that java.* packages are not imported.
+                    if (pkgName.startsWith("java."))
+                    {
+                        throw new BundleException(
+                            "Importing java.* packages not allowed: " + pkgName);
+                    }
+                    // The character "." has no meaning in the OSGi spec except
+                    // when placed on the bundle class path. Some people, however,
+                    // mistakenly think it means the default package when imported
+                    // or exported. This is not correct. It is invalid.
+                    else if (pkgName.equals("."))
+                    {
+                        throw new BundleException("Imporing '.' is invalid.");
+                    }
+                    // Make sure a package name was specified.
+                    else if (pkgName.length() == 0)
+                    {
+                        throw new BundleException(
+                            "Imported package names cannot be zero length.");
+                    }
+                    dupeSet.add(pkgName);
+                }
+                else
+                {
+                    throw new BundleException("Duplicate import: " + pkgName);
+                }
+            }
+
+            if (!mv.equals("2"))
+            {
+                // R3 bundles cannot have directives on their imports.
+                if (!clause.m_dirs.isEmpty())
+                {
+                    throw new BundleException("R3 imports cannot contain directives.");
+                }
+
+                // Remove and ignore all attributes other than version.
+                // NOTE: This is checking for "version" rather than "specification-version"
+                // because the package class normalizes to "version" to avoid having
+                // future special cases. This could be changed if more strict behavior
+                // is required.
+                if (!clause.m_attrs.isEmpty())
+                {
+                    // R3 package requirements should only have version attributes.
+                    Object pkgVersion = clause.m_attrs.get(BundleCapabilityImpl.VERSION_ATTR);
+                    pkgVersion = (pkgVersion == null)
+                        ? new VersionRange(Version.emptyVersion, true, null, true)
+                        : pkgVersion;
+                    for (Entry<String, Object> entry : clause.m_attrs.entrySet())
+                    {
+                        if (!entry.getKey().equals(BundleCapabilityImpl.VERSION_ATTR))
+                        {
+                            logger.log(Logger.LOG_WARNING,
+                                "Unknown R3 import attribute: "
+                                    + entry.getKey());
+                        }
+                    }
+
+                    // Remove all other attributes except package version.
+                    clause.m_attrs.clear();
+                    clause.m_attrs.put(BundleCapabilityImpl.VERSION_ATTR, pkgVersion);
+                }
+            }
+        }
+
+        return clauses;
+    }
+
+    public static List<BundleRequirement> parseDynamicImportHeader(
+        Logger logger, BundleRevision owner, String header)
+        throws BundleException
+    {
+
+        List<ParsedHeaderClause> importClauses = parseStandardHeader(header);
+        importClauses = normalizeDynamicImportClauses(logger, importClauses, "2");
+        List<BundleRequirement> reqs = convertImports(importClauses, owner);
+        return reqs;
+    }
+
+    private static List<BundleRequirement> convertImports(
+        List<ParsedHeaderClause> clauses, BundleRevision owner)
+    {
+        // Now convert generic header clauses into requirements.
+        List reqList = new ArrayList();
+        for (ParsedHeaderClause clause : clauses)
+        {
+            for (String path : clause.m_paths)
+            {
+                // Prepend the package name to the array of attributes.
+                Map<String, Object> attrs = clause.m_attrs;
+                // Note that we use a linked hash map here to ensure the
+                // package attribute is first, which will make indexing
+                // more efficient.
+// TODO: OSGi R4.3 - This is ordering is kind of hacky.
+                // Prepend the package name to the array of attributes.
+                Map<String, Object> newAttrs = new LinkedHashMap<String, Object>(attrs.size() + 1);
+                // We want this first from an indexing perspective.
+                newAttrs.put(
+                    BundleRevision.PACKAGE_NAMESPACE,
+                    path);
+                newAttrs.putAll(attrs);
+                // But we need to put it again to make sure it wasn't overwritten.
+                newAttrs.put(
+                    BundleRevision.PACKAGE_NAMESPACE,
+                    path);
+
+                // Create filter now so we can inject filter directive.
+                SimpleFilter sf = SimpleFilter.convert(newAttrs);
+
+                // Inject filter directive.
+// TODO: OSGi R4.3 - Can we insert this on demand somehow?
+                Map<String, String> dirs = clause.m_dirs;
+                Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1);
+                newDirs.putAll(dirs);
+                newDirs.put(
+                    Constants.FILTER_DIRECTIVE,
+                    sf.toString());
+
+                // Create package requirement and add to requirement list.
+                reqList.add(
+                    new BundleRequirementImpl(
+                        owner,
+                        BundleRevision.PACKAGE_NAMESPACE,
+                        newDirs,
+                        Collections.EMPTY_MAP,
+                        sf));
+            }
+        }
+
+        return reqList;
+    }
+
+    private static List<ParsedHeaderClause> normalizeDynamicImportClauses(
+        Logger logger, List<ParsedHeaderClause> clauses, String mv)
+        throws BundleException
+    {
+        // Verify that the values are equals if the package specifies
+        // both version and specification-version attributes.
+        for (ParsedHeaderClause clause : clauses)
+        {
+            if (!mv.equals("2"))
+            {
+                // R3 bundles cannot have directives on their imports.
+                if (!clause.m_dirs.isEmpty())
+                {
+                    throw new BundleException("R3 imports cannot contain directives.");
+                }
+            }
+
+            // Add the resolution directive to indicate that these are
+            // dynamic imports.
+            clause.m_dirs.put(Constants.RESOLUTION_DIRECTIVE,
+                FelixConstants.RESOLUTION_DYNAMIC);
+
+            // Check for "version" and "specification-version" attributes
+            // and verify they are the same if both are specified.
+            Object v = clause.m_attrs.get(Constants.VERSION_ATTRIBUTE);
+            Object sv = clause.m_attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION);
+            if ((v != null) && (sv != null))
+            {
+                // Verify they are equal.
+                if (!((String) v).trim().equals(((String) sv).trim()))
+                {
+                    throw new IllegalArgumentException(
+                        "Both version and specification-version are specified, but they are not equal.");
+                }
+            }
+
+            // Ensure that only the "version" attribute is used and convert
+            // it to the VersionRange type.
+            if ((v != null) || (sv != null))
+            {
+                clause.m_attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+                v = (v == null) ? sv : v;
+                clause.m_attrs.put(
+                    Constants.VERSION_ATTRIBUTE,
+                    VersionRange.parse(v.toString()));
+            }
+
+            // If bundle version is specified, then convert its type to VersionRange.
+            v = clause.m_attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+            if (v != null)
+            {
+                clause.m_attrs.put(
+                    Constants.BUNDLE_VERSION_ATTRIBUTE,
+                    VersionRange.parse(v.toString()));
+            }
+
+            // Dynamic imports can have duplicates, so verify that java.*
+            // packages are not imported.
+            for (String pkgName : clause.m_paths)
+            {
+                if (pkgName.startsWith("java."))
+                {
+                    throw new BundleException(
+                        "Dynamically importing java.* packages not allowed: " + pkgName);
+                }
+                else if (!pkgName.equals("*") && pkgName.endsWith("*") && !pkgName.endsWith(".*"))
+                {
+                    throw new BundleException(
+                        "Partial package name wild carding is not allowed: " + pkgName);
+                }
+            }
+        }
+
+        return clauses;
+    }
+
+    private static List<ParsedHeaderClause> normalizeRequireCapabilityClauses(
+        Logger logger, List<ParsedHeaderClause> clauses, String mv)
+        throws BundleException
+    {
+
+        if (!mv.equals("2") && !clauses.isEmpty())
+        {
+            // Should we error here if we are not an R4 bundle?
+        }
+
+        return clauses;
+    }
+
+    private static List<BundleRequirement> convertRequireCapabilities(
+        List<ParsedHeaderClause> clauses, BundleRevision owner)
+        throws BundleException
+    {
+        // Now convert generic header clauses into requirements.
+        List reqList = new ArrayList();
+        for (ParsedHeaderClause clause : clauses)
+        {
+            try
+            {
+                String filterStr = clause.m_dirs.get(Constants.FILTER_DIRECTIVE);
+                SimpleFilter sf = (filterStr != null)
+                    ? SimpleFilter.parse(filterStr)
+                    : new SimpleFilter(null, null, SimpleFilter.MATCH_ALL);
+                for (String path : clause.m_paths)
+                {
+                    // Create requirement and add to requirement list.
+                    reqList.add(
+                        new BundleRequirementImpl(
+                            owner,
+                            path,
+                            clause.m_dirs,
+                            clause.m_attrs,
+                            sf));
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new BundleException("Error creating requirement: " + ex);
+            }
+        }
+
+        return reqList;
+    }
+
+    private static List<ParsedHeaderClause> normalizeProvideCapabilityClauses(
+        Logger logger, List<ParsedHeaderClause> clauses, String mv)
+        throws BundleException
+    {
+
+        if (!mv.equals("2") && !clauses.isEmpty())
+        {
+            // Should we error here if we are not an R4 bundle?
+        }
+
+        // Convert attributes into specified types.
+        for (ParsedHeaderClause clause : clauses)
+        {
+            for (Entry<String, String> entry : clause.m_types.entrySet())
+            {
+                String type = entry.getValue();
+                if (!type.equals("String"))
+                {
+                    if (type.equals("Double"))
+                    {
+                        clause.m_attrs.put(
+                            entry.getKey(),
+                            new Double(clause.m_attrs.get(entry.getKey()).toString().trim()));
+                    }
+                    else if (type.equals("Version"))
+                    {
+                        clause.m_attrs.put(
+                            entry.getKey(),
+                            new Version(clause.m_attrs.get(entry.getKey()).toString().trim()));
+                    }
+                    else if (type.equals("Long"))
+                    {
+                        clause.m_attrs.put(
+                            entry.getKey(),
+                            new Long(clause.m_attrs.get(entry.getKey()).toString().trim()));
+                    }
+                    else if (type.startsWith("List"))
+                    {
+                        int startIdx = type.indexOf('<');
+                        int endIdx = type.indexOf('>');
+                        if (((startIdx > 0) && (endIdx <= startIdx))
+                            || ((startIdx < 0) && (endIdx > 0)))
+                        {
+                            throw new BundleException(
+                                "Invalid Provide-Capability attribute list type for '"
+                                + entry.getKey()
+                                + "' : "
+                                + type);
+                        }
+
+                        String listType = "String";
+                        if (endIdx > startIdx)
+                        {
+                            listType = type.substring(startIdx + 1, endIdx).trim();
+                        }
+
+                        List<String> tokens = parseDelimitedString(
+                            clause.m_attrs.get(entry.getKey()).toString(), ",", false);
+                        List<Object> values = new ArrayList<Object>(tokens.size());
+                        for (String token : tokens)
+                        {
+                            if (listType.equals("String"))
+                            {
+                                values.add(token);
+                            }
+                            else if (listType.equals("Double"))
+                            {
+                                values.add(new Double(token.trim()));
+                            }
+                            else if (listType.equals("Version"))
+                            {
+                                values.add(new Version(token.trim()));
+                            }
+                            else if (listType.equals("Long"))
+                            {
+                                values.add(new Long(token.trim()));
+                            }
+                            else
+                            {
+                                throw new BundleException(
+                                    "Unknown Provide-Capability attribute list type for '"
+                                    + entry.getKey()
+                                    + "' : "
+                                    + type);
+                            }
+                        }
+                        clause.m_attrs.put(
+                            entry.getKey(),
+                            values);
+                    }
+                    else
+                    {
+                        throw new BundleException(
+                            "Unknown Provide-Capability attribute type for '"
+                            + entry.getKey()
+                            + "' : "
+                            + type);
+                    }
+                }
+            }
+        }
+
+        return clauses;
+    }
+
+    private static List<BundleCapability> convertProvideCapabilities(
+        List<ParsedHeaderClause> clauses, BundleRevision owner)
+    {
+        List<BundleCapability> capList = new ArrayList();
+        for (ParsedHeaderClause clause : clauses)
+        {
+            for (String path : clause.m_paths)
+            {
+                // Create package capability and add to capability list.
+                capList.add(
+                    new BundleCapabilityImpl(
+                        owner,
+                        path,
+                        clause.m_dirs,
+                        clause.m_attrs));
+            }
+        }
+
+        return capList;
+    }
+
+    private static List<ParsedHeaderClause> normalizeExportClauses(
+        Logger logger, List<ParsedHeaderClause> clauses,
+        String mv, String bsn, Version bv)
+        throws BundleException
+    {
+        // Verify that "java.*" packages are not exported.
+        for (ParsedHeaderClause clause : clauses)
+        {
+            // Verify that the named package has not already been declared.
+            for (String pkgName : clause.m_paths)
+            {
+                // Verify that java.* packages are not exported.
+                if (pkgName.startsWith("java."))
+                {
+                    throw new BundleException(
+                        "Exporting java.* packages not allowed: "
+                        + pkgName);
+                }
+                // The character "." has no meaning in the OSGi spec except
+                // when placed on the bundle class path. Some people, however,
+                // mistakenly think it means the default package when imported
+                // or exported. This is not correct. It is invalid.
+                else if (pkgName.equals("."))
+                {
+                    throw new BundleException("Exporing '.' is invalid.");
+                }
+                // Make sure a package name was specified.
+                else if (pkgName.length() == 0)
+                {
+                    throw new BundleException(
+                        "Exported package names cannot be zero length.");
+                }
+            }
+
+            // Check for "version" and "specification-version" attributes
+            // and verify they are the same if both are specified.
+            Object v = clause.m_attrs.get(Constants.VERSION_ATTRIBUTE);
+            Object sv = clause.m_attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION);
+            if ((v != null) && (sv != null))
+            {
+                // Verify they are equal.
+                if (!((String) v).trim().equals(((String) sv).trim()))
+                {
+                    throw new IllegalArgumentException(
+                        "Both version and specification-version are specified, but they are not equal.");
+                }
+            }
+
+            // Always add the default version if not specified.
+            if ((v == null) && (sv == null))
+            {
+                v = Version.emptyVersion;
+            }
+
+            // 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.
+                clause.m_attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+                v = (v == null) ? sv : v;
+                clause.m_attrs.put(
+                    Constants.VERSION_ATTRIBUTE,
+                    Version.parseVersion(v.toString()));
+            }
+
+            // If this is an R4 bundle, then make sure it doesn't specify
+            // bundle symbolic name or bundle version attributes.
+            if (mv.equals("2"))
+            {
+                // Find symbolic name and version attribute, if present.
+                if (clause.m_attrs.containsKey(Constants.BUNDLE_VERSION_ATTRIBUTE)
+                    || clause.m_attrs.containsKey(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE))
+                {
+                    throw new BundleException(
+                        "Exports must not specify bundle symbolic name or bundle version.");
+                }
+
+                // Now that we know that there are no bundle symbolic name and version
+                // attributes, add them since the spec says they are there implicitly.
+                clause.m_attrs.put(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, bsn);
+                clause.m_attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, bv);
+            }
+            else if (!mv.equals("2"))
+            {
+                // R3 bundles cannot have directives on their exports.
+                if (!clause.m_dirs.isEmpty())
+                {
+                    throw new BundleException("R3 exports cannot contain directives.");
+                }
+
+                // Remove and ignore all attributes other than version.
+                // NOTE: This is checking for "version" rather than "specification-version"
+                // because the package class normalizes to "version" to avoid having
+                // future special cases. This could be changed if more strict behavior
+                // is required.
+                if (!clause.m_attrs.isEmpty())
+                {
+                    // R3 package capabilities should only have a version attribute.
+                    Object pkgVersion = clause.m_attrs.get(BundleCapabilityImpl.VERSION_ATTR);
+                    pkgVersion = (pkgVersion == null)
+                        ? Version.emptyVersion
+                        : pkgVersion;
+                    for (Entry<String, Object> entry : clause.m_attrs.entrySet())
+                    {
+                        if (!entry.getKey().equals(BundleCapabilityImpl.VERSION_ATTR))
+                        {
+                            logger.log(
+                                Logger.LOG_WARNING,
+                                "Unknown R3 export attribute: "
+                                + entry.getKey());
+                        }
+                    }
+
+                    // Remove all other attributes except package version.
+                    clause.m_attrs.clear();
+                    clause.m_attrs.put(BundleCapabilityImpl.VERSION_ATTR, pkgVersion);
+                }
+            }
+        }
+
+        return clauses;
+    }
+
+    private static List<BundleCapability> convertExports(
+        List<ParsedHeaderClause> clauses, BundleRevision owner)
+    {
+        List<BundleCapability> capList = new ArrayList();
+        for (ParsedHeaderClause clause : clauses)
+        {
+            for (String pkgName : clause.m_paths)
+            {
+                // Prepend the package name to the array of attributes.
+                Map<String, Object> attrs = clause.m_attrs;
+                Map<String, Object> newAttrs = new HashMap<String, Object>(attrs.size() + 1);
+                newAttrs.putAll(attrs);
+                newAttrs.put(
+                    BundleRevision.PACKAGE_NAMESPACE,
+                    pkgName);
+
+                // Create package capability and add to capability list.
+                capList.add(
+                    new BundleCapabilityImpl(
+                        owner,
+                        BundleRevision.PACKAGE_NAMESPACE,
+                        clause.m_dirs,
+                        newAttrs));
+            }
+        }
+
+        return capList;
+    }
+
+    public String getManifestVersion()
+    {
+        String manifestVersion = getManifestVersion(m_headerMap);
+        return (manifestVersion == null) ? "1" : manifestVersion;
+    }
+
+    private static String getManifestVersion(Map headerMap)
+    {
+        String manifestVersion = (String) headerMap.get(Constants.BUNDLE_MANIFESTVERSION);
+        return (manifestVersion == null) ? null : manifestVersion.trim();
+    }
+
+    public int getActivationPolicy()
+    {
+        return m_activationPolicy;
+    }
+
+    public String getActivationIncludeDirective()
+    {
+        return m_activationIncludeDir;
+    }
+
+    public String getActivationExcludeDirective()
+    {
+        return m_activationExcludeDir;
+    }
+
+    public boolean isExtension()
+    {
+        return m_isExtension;
+    }
+
+    public String getSymbolicName()
+    {
+        return m_bundleSymbolicName;
+    }
+
+    public Version getBundleVersion()
+    {
+        return m_bundleVersion;
+    }
+
+    public List<BundleCapability> getCapabilities()
+    {
+        return m_capabilities;
+    }
+
+    public List<BundleRequirement> getRequirements()
+    {
+        return m_requirements;
+    }
+
+    public List<R4LibraryClause> getLibraryClauses()
+    {
+        return m_libraryClauses;
+    }
+
+    /**
+     * <p>
+     * This method returns the selected native library metadata from
+     * the manifest. The information is not the raw metadata from the
+     * manifest, but is the native library clause selected according
+     * to the OSGi native library clause selection policy. The metadata
+     * returned by this method will be attached directly to a module and
+     * used for finding its native libraries at run time. To inspect the
+     * raw native library metadata refer to <tt>getLibraryClauses()</tt>.
+     * </p>
+     * <p>
+     * This method returns one of three values:
+     * </p>
+     * <ul>
+     * <li><tt>null</tt> - if the are no native libraries for this module;
+     *     this may also indicate the native libraries are optional and
+     *     did not match the current platform.</li>
+     * <li>Zero-length <tt>R4Library</tt> array - if no matching native library
+     *     clause was found; this bundle should not resolve.</li>
+     * <li>Nonzero-length <tt>R4Library</tt> array - the native libraries
+     *     associated with the matching native library clause.</li>
+     * </ul>
+     *
+     * @return <tt>null</tt> if there are no native libraries, a zero-length
+     *         array if no libraries matched, or an array of selected libraries.
+    **/
+    public List<R4Library> getLibraries()
+    {
+        ArrayList<R4Library> libs = null;
+        try
+        {
+            R4LibraryClause clause = getSelectedLibraryClause();
+            if (clause != null)
+            {
+                String[] entries = clause.getLibraryEntries();
+                libs = new ArrayList<R4Library>(entries.length);
+                int current = 0;
+                for (int i = 0; i < entries.length; i++)
+                {
+                    String name = getName(entries[i]);
+                    boolean found = false;
+                    for (int j = 0; !found && (j < current); j++)
+                    {
+                        found = getName(entries[j]).equals(name);
+                    }
+                    if (!found)
+                    {
+                        libs.add(new R4Library(
+                            clause.getLibraryEntries()[i],
+                            clause.getOSNames(), clause.getProcessors(), clause.getOSVersions(),
+                            clause.getLanguages(), clause.getSelectionFilter()));
+                    }
+                }
+                libs.trimToSize();
+            }
+        }
+        catch (Exception ex)
+        {
+            libs = new ArrayList<R4Library>(0);
+        }
+        return libs;
+    }
+
+    private String getName(String path)
+    {
+        int idx = path.lastIndexOf('/');
+        if (idx > -1)
+        {
+            return path.substring(idx);
+        }
+        return path;
+    }
+
+    private R4LibraryClause getSelectedLibraryClause() throws BundleException
+    {
+        if ((m_libraryClauses != null) && (m_libraryClauses.size() > 0))
+        {
+            List clauseList = new ArrayList();
+
+            // Search for matching native clauses.
+            for (R4LibraryClause libraryClause : m_libraryClauses)
+            {
+                if (libraryClause.match(m_configMap))
+                {
+                    clauseList.add(libraryClause);
+                }
+            }
+
+            // Select the matching native clause.
+            int selected = 0;
+            if (clauseList.isEmpty())
+            {
+                // 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<R4LibraryClause> clauseList)
+    {
+        ArrayList indexList = new ArrayList();
+        ArrayList selection = new ArrayList();
+
+        // Init index list
+        for (int i = 0; i < clauseList.size(); i++)
+        {
+            indexList.add("" + i);
+        }
+
+        // Select clause with 'osversion' range declared
+        // and get back the max floor of 'osversion' ranges.
+        Version osVersionRangeMaxFloor = new Version(0, 0, 0);
+        for (int i = 0; i < indexList.size(); i++)
+        {
+            int index = Integer.parseInt(indexList.get(i).toString());
+            String[] osversions = ((R4LibraryClause) clauseList.get(index)).getOSVersions();
+            if (osversions != null)
+            {
+                selection.add("" + indexList.get(i));
+            }
+            for (int k = 0; (osversions != null) && (k < osversions.length); k++)
+            {
+                VersionRange range = VersionRange.parse(osversions[k]);
+                if ((range.getFloor()).compareTo(osVersionRangeMaxFloor) >= 0)
+                {
+                    osVersionRangeMaxFloor = range.getFloor();
+                }
+            }
+        }
+
+        if (selection.size() == 1)
+        {
+            return Integer.parseInt(selection.get(0).toString());
+        }
+        else if (selection.size() > 1)
+        {
+            // Keep only selected clauses with an 'osversion'
+            // equal to the max floor of 'osversion' ranges.
+            indexList = selection;
+            selection = new ArrayList();
+            for (int i = 0; i < indexList.size(); i++)
+            {
+                int index = Integer.parseInt(indexList.get(i).toString());
+                String[] osversions = ((R4LibraryClause) clauseList.get(index)).getOSVersions();
+                for (int k = 0; k < osversions.length; k++)
+                {
+                    VersionRange range = VersionRange.parse(osversions[k]);
+                    if ((range.getFloor()).compareTo(osVersionRangeMaxFloor) >= 0)
+                    {
+                        selection.add("" + indexList.get(i));
+                    }
+                }
+            }
+        }
+
+        if (selection.isEmpty())
+        {
+            // 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.isEmpty())
+        {
+            return 0;
+        }
+        else
+        {
+            return Integer.parseInt(selection.get(0).toString());
+        }
+    }
+
+    private static List<ParsedHeaderClause> calculateImplicitImports(
+        List<BundleCapability> exports, List<ParsedHeaderClause> imports)
+        throws BundleException
+    {
+        List<ParsedHeaderClause> clauseList = new ArrayList();
+
+        // Since all R3 exports imply an import, add a corresponding
+        // requirement for each existing export capability. Do not
+        // duplicate imports.
+        Map map =  new HashMap();
+        // Add existing imports.
+        for (int impIdx = 0; impIdx < imports.size(); impIdx++)
+        {
+            for (int pathIdx = 0; pathIdx < imports.get(impIdx).m_paths.size(); pathIdx++)
+            {
+                map.put(
+                    imports.get(impIdx).m_paths.get(pathIdx),
+                    imports.get(impIdx).m_paths.get(pathIdx));
+            }
+        }
+        // Add import requirement for each export capability.
+        for (int i = 0; i < exports.size(); i++)
+        {
+            if (map.get(exports.get(i).getAttributes()
+                .get(BundleRevision.PACKAGE_NAMESPACE)) == null)
+            {
+                // Convert Version to VersionRange.
+                Map<String, Object> attrs = new HashMap<String, Object>();
+                Object version = exports.get(i).getAttributes().get(Constants.VERSION_ATTRIBUTE);
+                if (version != null)
+                {
+                    attrs.put(
+                        Constants.VERSION_ATTRIBUTE,
+                        VersionRange.parse(version.toString()));
+                }
+
+                List<String> paths = new ArrayList();
+                paths.add((String)
+                    exports.get(i).getAttributes().get(BundleRevision.PACKAGE_NAMESPACE));
+                clauseList.add(
+                    new ParsedHeaderClause(
+                        paths, Collections.EMPTY_MAP, attrs, Collections.EMPTY_MAP));
+            }
+        }
+
+        return clauseList;
+    }
+
+    private static List<BundleCapability> calculateImplicitUses(
+        List<BundleCapability> exports, List<ParsedHeaderClause> imports)
+        throws BundleException
+    {
+        // Add a "uses" directive onto each export of R3 bundles
+        // that references every other import (which will include
+        // exports, since export implies import); this is
+        // necessary since R3 bundles assumed a single class space,
+        // but R4 allows for multiple class spaces.
+        String usesValue = "";
+        for (int i = 0; i < imports.size(); i++)
+        {
+            for (int pathIdx = 0; pathIdx < imports.get(i).m_paths.size(); pathIdx++)
+            {
+                usesValue = usesValue
+                    + ((usesValue.length() > 0) ? "," : "")
+                    + imports.get(i).m_paths.get(pathIdx);
+            }
+        }
+        for (int i = 0; i < exports.size(); i++)
+        {
+            Map<String, String> dirs = new HashMap<String, String>(1);
+            dirs.put(Constants.USES_DIRECTIVE, usesValue);
+            exports.set(i, new BundleCapabilityImpl(
+                exports.get(i).getRevision(),
+                BundleRevision.PACKAGE_NAMESPACE,
+                dirs,
+                exports.get(i).getAttributes()));
+        }
+
+        return exports;
+    }
+
+    private static boolean checkExtensionBundle(Map headerMap) throws BundleException
+    {
+        Object extension = parseExtensionBundleHeader(
+            (String) headerMap.get(Constants.FRAGMENT_HOST));
+
+        if (extension != null)
+        {
+            if (!(Constants.EXTENSION_FRAMEWORK.equals(extension) ||
+                Constants.EXTENSION_BOOTCLASSPATH.equals(extension)))
+            {
+                throw new BundleException(
+                    "Extension bundle must have either 'extension:=framework' or 'extension:=bootclasspath'");
+            }
+            if (headerMap.containsKey(Constants.IMPORT_PACKAGE) ||
+                headerMap.containsKey(Constants.REQUIRE_BUNDLE) ||
+                headerMap.containsKey(Constants.BUNDLE_NATIVECODE) ||
+                headerMap.containsKey(Constants.DYNAMICIMPORT_PACKAGE) ||
+                headerMap.containsKey(Constants.BUNDLE_ACTIVATOR))
+            {
+                throw new BundleException("Invalid extension bundle manifest");
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private static BundleCapabilityImpl parseBundleSymbolicName(
+        BundleRevision owner, Map headerMap)
+        throws BundleException
+    {
+        List<ParsedHeaderClause> clauses = parseStandardHeader(
+            (String) headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
+        if (clauses.size() > 0)
+        {
+            if (clauses.size() > 1)
+            {
+                throw new BundleException(
+                    "Cannot have multiple symbolic names: "
+                        + headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
+            }
+            else if (clauses.get(0).m_paths.size() > 1)
+            {
+                throw new BundleException(
+                    "Cannot have multiple symbolic names: "
+                        + headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
+            }
+
+            // Get bundle version.
+            Version bundleVersion = Version.emptyVersion;
+            if (headerMap.get(Constants.BUNDLE_VERSION) != null)
+            {
+                try
+                {
+                    bundleVersion = Version.parseVersion(
+                        (String) headerMap.get(Constants.BUNDLE_VERSION));
+                }
+                catch (RuntimeException ex)
+                {
+                    // R4 bundle versions must parse, R3 bundle version may not.
+                    String mv = getManifestVersion(headerMap);
+                    if (mv != null)
+                    {
+                        throw ex;
+                    }
+                    bundleVersion = Version.emptyVersion;
+                }
+            }
+
+            // Create a require capability and return it.
+            String symName = (String) clauses.get(0).m_paths.get(0);
+            clauses.get(0).m_attrs.put(BundleRevision.BUNDLE_NAMESPACE, symName);
+            clauses.get(0).m_attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, bundleVersion);
+            return new BundleCapabilityImpl(
+                owner,
+                BundleRevision.BUNDLE_NAMESPACE,
+                clauses.get(0).m_dirs,
+                clauses.get(0).m_attrs);
+        }
+
+        return null;
+    }
+
+    private static List<BundleRequirementImpl> parseFragmentHost(
+        Logger logger, BundleRevision owner, Map headerMap)
+        throws BundleException
+    {
+        List<BundleRequirementImpl> reqs = new ArrayList();
+
+        String mv = getManifestVersion(headerMap);
+        if ((mv != null) && mv.equals("2"))
+        {
+            List<ParsedHeaderClause> clauses = parseStandardHeader(
+                (String) headerMap.get(Constants.FRAGMENT_HOST));
+            if (clauses.size() > 0)
+            {
+                // Make sure that only one fragment host symbolic name is specified.
+                if (clauses.size() > 1)
+                {
+                    throw new BundleException(
+                        "Fragments cannot have multiple hosts: "
+                            + headerMap.get(Constants.FRAGMENT_HOST));
+                }
+                else if (clauses.get(0).m_paths.size() > 1)
+                {
+                    throw new BundleException(
+                        "Fragments cannot have multiple hosts: "
+                            + headerMap.get(Constants.FRAGMENT_HOST));
+                }
+
+                // If the bundle-version attribute is specified, then convert
+                // it to the proper type.
+                Object value = clauses.get(0).m_attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+                value = (value == null) ? "0.0.0" : value;
+                if (value != null)
+                {
+                    clauses.get(0).m_attrs.put(
+                        Constants.BUNDLE_VERSION_ATTRIBUTE,
+                        VersionRange.parse(value.toString()));
+                }
+
+                // Note that we use a linked hash map here to ensure the
+                // host symbolic name is first, which will make indexing
+                // more efficient.
+// TODO: OSGi R4.3 - This is ordering is kind of hacky.
+                // Prepend the host symbolic name to the map of attributes.
+                Map<String, Object> attrs = clauses.get(0).m_attrs;
+                Map<String, Object> newAttrs = new LinkedHashMap<String, Object>(attrs.size() + 1);
+                // We want this first from an indexing perspective.
+                newAttrs.put(
+                    BundleRevision.HOST_NAMESPACE,
+                    clauses.get(0).m_paths.get(0));
+                newAttrs.putAll(attrs);
+                // But we need to put it again to make sure it wasn't overwritten.
+                newAttrs.put(
+                    BundleRevision.HOST_NAMESPACE,
+                    clauses.get(0).m_paths.get(0));
+
+                // Create filter now so we can inject filter directive.
+                SimpleFilter sf = SimpleFilter.convert(newAttrs);
+
+                // Inject filter directive.
+// TODO: OSGi R4.3 - Can we insert this on demand somehow?
+                Map<String, String> dirs = clauses.get(0).m_dirs;
+                Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1);
+                newDirs.putAll(dirs);
+                newDirs.put(
+                    Constants.FILTER_DIRECTIVE,
+                    sf.toString());
+
+                reqs.add(new BundleRequirementImpl(
+                    owner, BundleRevision.HOST_NAMESPACE,
+                    newDirs,
+                    newAttrs));
+            }
+        }
+        else if (headerMap.get(Constants.FRAGMENT_HOST) != null)
+        {
+            String s = (String) headerMap.get(Constants.BUNDLE_SYMBOLICNAME);
+            s = (s == null) ? (String) headerMap.get(Constants.BUNDLE_NAME) : s;
+            s = (s == null) ? headerMap.toString() : s;
+            logger.log(
+                owner.getBundle(),
+                Logger.LOG_WARNING,
+                "Only R4 bundles can be fragments: " + s);
+        }
+
+        return reqs;
+    }
+
+    public static List<BundleCapability> parseExportHeader(
+        Logger logger, BundleRevision owner, String header, String bsn, Version bv)
+    {
+
+        List<BundleCapability> caps = null;
+        try
+        {
+            List<ParsedHeaderClause> exportClauses = parseStandardHeader(header);
+            exportClauses = normalizeExportClauses(logger, exportClauses, "2", bsn, bv);
+            caps = convertExports(exportClauses, owner);
+        }
+        catch (BundleException ex)
+        {
+            caps = null;
+        }
+        return caps;
+    }
+
+    private static List<ParsedHeaderClause> normalizeRequireClauses(
+        Logger logger, List<ParsedHeaderClause> clauses, String mv)
+    {
+        // R3 bundles cannot require other bundles.
+        if (!mv.equals("2"))
+        {
+            clauses.clear();
+        }
+        else
+        {
+            // Convert bundle version attribute to VersionRange type.
+            for (ParsedHeaderClause clause : clauses)
+            {
+                Object value = clause.m_attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+                if (value != null)
+                {
+                    clause.m_attrs.put(
+                        Constants.BUNDLE_VERSION_ATTRIBUTE,
+                        VersionRange.parse(value.toString()));
+                }
+            }
+        }
+
+        return clauses;
+    }
+
+    private static List<BundleRequirementImpl> convertRequires(
+        List<ParsedHeaderClause> clauses, BundleRevision owner)
+    {
+        List<BundleRequirementImpl> reqList = new ArrayList();
+        for (ParsedHeaderClause clause : clauses)
+        {
+            for (String path : clause.m_paths)
+            {
+                // Prepend the bundle symbolic name to the array of attributes.
+                Map<String, Object> attrs = clause.m_attrs;
+                // Note that we use a linked hash map here to ensure the
+                // symbolic name attribute is first, which will make indexing
+                // more efficient.
+// TODO: OSGi R4.3 - This is ordering is kind of hacky.
+                // Prepend the symbolic name to the array of attributes.
+                Map<String, Object> newAttrs = new LinkedHashMap<String, Object>(attrs.size() + 1);
+                // We want this first from an indexing perspective.
+                newAttrs.put(
+                    BundleRevision.BUNDLE_NAMESPACE,
+                    path);
+                newAttrs.putAll(attrs);
+                // But we need to put it again to make sure it wasn't overwritten.
+                newAttrs.put(
+                    BundleRevision.BUNDLE_NAMESPACE,
+                    path);
+
+                // Create filter now so we can inject filter directive.
+                SimpleFilter sf = SimpleFilter.convert(newAttrs);
+
+                // Inject filter directive.
+// TODO: OSGi R4.3 - Can we insert this on demand somehow?
+                Map<String, String> dirs = clause.m_dirs;
+                Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1);
+                newDirs.putAll(dirs);
+                newDirs.put(
+                    Constants.FILTER_DIRECTIVE,
+                    sf.toString());
+
+                // Create package requirement and add to requirement list.
+                reqList.add(
+                    new BundleRequirementImpl(
+                        owner,
+                        BundleRevision.BUNDLE_NAMESPACE,
+                        newDirs,
+                        newAttrs));
+            }
+        }
+
+        return reqList;
+    }
+
+    public static String parseExtensionBundleHeader(String header)
+        throws BundleException
+    {
+        List<ParsedHeaderClause> clauses = parseStandardHeader(header);
+
+        String result = null;
+
+        if (clauses.size() == 1)
+        {
+            // See if there is the "extension" directive.
+            for (Entry<String, String> entry : clauses.get(0).m_dirs.entrySet())
+            {
+                if (Constants.EXTENSION_DIRECTIVE.equals(entry.getKey()))
+                {
+                    // If the extension directive is specified, make sure
+                    // the target is the system bundle.
+                    if (FelixConstants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(clauses.get(0).m_paths.get(0)) ||
+                        Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(clauses.get(0).m_paths.get(0)))
+                    {
+                        return entry.getValue();
+                    }
+                    else
+                    {
+                        throw new BundleException(
+                            "Only the system bundle can have extension bundles.");
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+    private void parseActivationPolicy(Map headerMap)
+    {
+        m_activationPolicy = FelixConstants.EAGER_ACTIVATION;
+
+        List<ParsedHeaderClause> clauses = parseStandardHeader(
+            (String) headerMap.get(Constants.BUNDLE_ACTIVATIONPOLICY));
+
+        if (clauses.size() > 0)
+        {
+            // Just look for a "path" matching the lazy policy, ignore
+            // everything else.
+            for (String path : clauses.get(0).m_paths)
+            {
+                if (path.equals(Constants.ACTIVATION_LAZY))
+                {
+                    m_activationPolicy = FelixConstants.LAZY_ACTIVATION;
+                    for (Entry<String, String> entry : clauses.get(0).m_dirs.entrySet())
+                    {
+                        if (entry.getKey().equalsIgnoreCase(Constants.INCLUDE_DIRECTIVE))
+                        {
+                            m_activationIncludeDir = entry.getValue();
+                        }
+                        else if (entry.getKey().equalsIgnoreCase(Constants.EXCLUDE_DIRECTIVE))
+                        {
+                            m_activationExcludeDir = entry.getValue();
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    // Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2,
+    //            path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
+    public static void main(String[] headers)
+    {
+        String header = headers[0];
+
+        if (header != null)
+        {
+            if (header.length() == 0)
+            {
+                throw new IllegalArgumentException(
+                    "A header cannot be an empty string.");
+            }
+
+            List<ParsedHeaderClause> clauses = parseStandardHeader(header);
+            for (ParsedHeaderClause clause : clauses)
+            {
+                System.out.println("PATHS " + clause.m_paths);
+                System.out.println("    DIRS  " + clause.m_dirs);
+                System.out.println("    ATTRS " + clause.m_attrs);
+                System.out.println("    TYPES " + clause.m_types);
+            }
+        }
+
+//        return clauses;
+    }
+
+    private static List<ParsedHeaderClause> parseStandardHeader(String header)
+    {
+        List<ParsedHeaderClause> clauses = new ArrayList<ParsedHeaderClause>();
+        if (header != null)
+        {
+            int[] startIdx = new int[1];
+            startIdx[0] = 0;
+            for (int i = 0; i < header.length(); i++)
+            {
+                clauses.add(parseClause(startIdx, header));
+                i = startIdx[0];
+            }
+        }
+        return clauses;
+    }
+
+    private static ParsedHeaderClause parseClause(int[] startIdx, String header)
+    {
+        ParsedHeaderClause clause = new ParsedHeaderClause(
+            new ArrayList<String>(),
+            new HashMap<String, String>(),
+            new HashMap<String, Object>(),
+            new HashMap<String, String>());
+        for (int i = startIdx[0]; i < header.length(); i++)
+        {
+            char c = header.charAt(i);
+            if ((c == ':') || (c == '='))
+            {
+                parseClauseParameters(startIdx, header, clause);
+                i = startIdx[0];
+                break;
+            }
+            else if ((c == ';') || (c == ',') || (i == (header.length() - 1)))
+            {
+                String path;
+                if (i == (header.length() - 1))
+                {
+                    path = header.substring(startIdx[0], header.length());
+                }
+                else
+                {
+                    path = header.substring(startIdx[0], i);
+                }
+                clause.m_paths.add(path.trim());
+                startIdx[0] = i + 1;
+                if (c == ',')
+                {
+                    break;
+                }
+            }
+        }
+        return clause;
+    }
+
+    private static void parseClauseParameters(
+        int[] startIdx, String header, ParsedHeaderClause clause)
+    {
+        for (int i = startIdx[0]; i < header.length(); i++)
+        {
+            char c = header.charAt(i);
+            if ((c == ':') && (header.charAt(i + 1) == '='))
+            {
+                parseClauseDirective(startIdx, header, clause);
+                i = startIdx[0];
+            }
+            else if ((c == ':') || (c == '='))
+            {
+                parseClauseAttribute(startIdx, header, clause);
+                i = startIdx[0];
+            }
+            else if (c == ',')
+            {
+                startIdx[0] = i + 1;
+                break;
+            }
+        }
+    }
+
+    private static void parseClauseDirective(
+        int[] startIdx, String header, ParsedHeaderClause clause)
+    {
+        String name = null;
+        String value = null;
+        boolean isQuoted = false;
+        boolean isEscaped = false;
+        for (int i = startIdx[0]; i < header.length(); i++)
+        {
+            char c = header.charAt(i);
+            if (!isEscaped && (c == '"'))
+            {
+                isQuoted = !isQuoted;
+            }
+
+            if (!isEscaped
+                && !isQuoted && (c == ':'))
+            {
+                name = header.substring(startIdx[0], i);
+                startIdx[0] = i + 2;
+            }
+            else if (!isEscaped
+                && !isQuoted && ((c == ';') || (c == ',') || (i == (header.length() - 1))))
+            {
+                if (i == (header.length() - 1))
+                {
+                    value = header.substring(startIdx[0], header.length());
+                }
+                else
+                {
+                    value = header.substring(startIdx[0], i);
+                }
+                if (c == ',')
+                {
+                    startIdx[0] = i - 1;
+                }
+                else
+                {
+                    startIdx[0] = i + 1;
+                }
+                break;
+            }
+
+            isEscaped = (c == '\\');
+        }
+
+        // Trim whitespace.
+        name = name.trim();
+        value = value.trim();
+
+        // Remove quotes, if value is quoted.
+        if (value.startsWith("\"") && value.endsWith("\""))
+        {
+            value = value.substring(1, value.length() - 1);
+        }
+
+        // Check for dupes.
+        if (clause.m_dirs.get(name) != null)
+        {
+            throw new IllegalArgumentException(
+                "Duplicate directive '" + name + "' in: " + header);
+        }
+
+        clause.m_dirs.put(name, value);
+    }
+
+    private static void parseClauseAttribute(
+        int[] startIdx, String header, ParsedHeaderClause clause)
+    {
+        String type = null;
+
+        String name = parseClauseAttributeName(startIdx, header);
+        char c = header.charAt(startIdx[0]);
+        startIdx[0]++;
+        if (c == ':')
+        {
+            type = parseClauseAttributeType(startIdx, header);
+        }
+
+        String value = parseClauseAttributeValue(startIdx, header);
+
+        // Trim whitespace.
+        name = name.trim();
+        value = value.trim();
+        if (type != null)
+        {
+            type = type.trim();
+        }
+
+        // Remove quotes, if value is quoted.
+        if (value.startsWith("\"") && value.endsWith("\""))
+        {
+            value = value.substring(1, value.length() - 1);
+        }
+
+        // Check for dupes.
+        if (clause.m_attrs.get(name) != null)
+        {
+            throw new IllegalArgumentException(
+                "Duplicate attribute '" + name + "' in: " + header);
+        }
+
+        clause.m_attrs.put(name, value);
+        if (type != null)
+        {
+            clause.m_types.put(name, type);
+        }
+    }
+
+    private static String parseClauseAttributeName(int[] startIdx, String header)
+    {
+        for (int i = startIdx[0]; i < header.length(); i++)
+        {
+            char c = header.charAt(i);
+            if ((c == '=') || (c == ':'))
+            {
+                String name = header.substring(startIdx[0], i);
+                startIdx[0] = i;
+                return name;
+            }
+        }
+        return null;
+    }
+
+    private static String parseClauseAttributeType(int[] startIdx, String header)
+    {
+        for (int i = startIdx[0]; i < header.length(); i++)
+        {
+            char c = header.charAt(i);
+            if (c == '=')
+            {
+                String type = header.substring(startIdx[0], i);
+                startIdx[0] = i + 1;
+                return type;
+            }
+        }
+        return null;
+    }
+
+    private static String parseClauseAttributeValue(int[] startIdx, String header)
+    {
+        boolean isQuoted = false;
+        boolean isEscaped = false;
+        for (int i = startIdx[0]; i < header.length(); i++)
+        {
+            char c = header.charAt(i);
+            if (!isEscaped && (c == '"'))
+            {
+                isQuoted = !isQuoted;
+            }
+
+            if (!isEscaped &&
+                !isQuoted && ((c == ';') || (c == ',') || (i == (header.length() - 1))))
+            {
+                String value;
+                if (i == (header.length() - 1))
+                {
+                    value = header.substring(startIdx[0], header.length());
+                }
+                else
+                {
+                    value = header.substring(startIdx[0], i);
+                }
+                if (c == ',')
+                {
+                    startIdx[0] = i - 1;
+                }
+                else
+                {
+                    startIdx[0] = i + 1;
+                }
+                return value;
+            }
+
+            isEscaped = (c == '\\');
+        }
+        return null;
+    }
+
+    public static List<String> parseDelimitedString(String value, String delim)
+    {
+        return parseDelimitedString(value, delim, true);
+    }
+
+    /**
+     * 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 a list of string or an empty list if there are none.
+    **/
+    public static List<String> parseDelimitedString(String value, String delim, boolean trim)
+    {
+        if (value == null)
+        {
+           value = "";
+        }
+
+        List<String> list = new ArrayList();
+
+        int CHAR = 1;
+        int DELIMITER = 2;
+        int STARTQUOTE = 4;
+        int ENDQUOTE = 8;
+
+        StringBuffer sb = new StringBuffer();
+
+        int expecting = (CHAR | DELIMITER | STARTQUOTE);
+
+        boolean isEscaped = false;
+        for (int i = 0; i < value.length(); i++)
+        {
+            char c = value.charAt(i);
+
+            boolean isDelimiter = (delim.indexOf(c) >= 0);
+
+            if (c == '\\')
+            {
+                isEscaped = true;
+                continue;
+            }
+
+            if (isEscaped)
+            {
+                sb.append(c);
+            }
+            else if (isDelimiter && ((expecting & DELIMITER) > 0))
+            {
+                if (trim)
+                {
+                    list.add(sb.toString().trim());
+                }
+                else
+                {
+                    list.add(sb.toString());
+                }
+                sb.delete(0, sb.length());
+                expecting = (CHAR | DELIMITER | STARTQUOTE);
+            }
+            else if ((c == '"') && ((expecting & STARTQUOTE) > 0))
+            {
+                sb.append(c);
+                expecting = CHAR | ENDQUOTE;
+            }
+            else if ((c == '"') && ((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);
+            }
+
+            isEscaped = false;
+        }
+
+        if (sb.length() > 0)
+        {
+            if (trim)
+            {
+                list.add(sb.toString().trim());
+            }
+            else
+            {
+                list.add(sb.toString());
+            }
+        }
+
+        return list;
+    }
+
+    /**
+     * Parses native code manifest headers.
+     * @param libStrs an array of native library manifest header
+     *        strings from the bundle manifest.
+     * @return an array of <tt>LibraryInfo</tt> objects for the
+     *         passed in strings.
+    **/
+    private static List<R4LibraryClause> parseLibraryStrings(
+        Logger logger, List<String> libStrs)
+        throws IllegalArgumentException
+    {
+        if (libStrs == null)
+        {
+            return new ArrayList<R4LibraryClause>(0);
+        }
+
+        List<R4LibraryClause> libList = new ArrayList(libStrs.size());
+
+        for (int i = 0; i < libStrs.size(); i++)
+        {
+            R4LibraryClause clause = R4LibraryClause.parse(logger, libStrs.get(i));
+            libList.add(clause);
+        }
+
+        return libList;
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/manifestparser/ParsedHeaderClause.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/manifestparser/ParsedHeaderClause.java?rev=1464402&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/manifestparser/ParsedHeaderClause.java (added)
+++ ace/trunk/org.apache.ace.verifier/src/org/apache/felix/framework/util/manifestparser/ParsedHeaderClause.java Thu Apr  4 09:43:34 2013
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework.util.manifestparser;
+
+import java.util.List;
+import java.util.Map;
+
+public class ParsedHeaderClause
+{
+    public final List<String> m_paths;
+    public final Map<String, String> m_dirs;
+    public final Map<String, Object> m_attrs;
+    public final Map<String, String> m_types;
+
+    public ParsedHeaderClause(
+        List<String> paths, Map<String, String> dirs, Map<String, Object> attrs,
+        Map<String, String> types)
+    {
+        m_paths = paths;
+        m_dirs = dirs;
+        m_attrs = attrs;
+        m_types = types;
+    }
+}
\ No newline at end of file



Mime
View raw message