Moved manifest parsing out of Felix.java and into its own utility class.
Also strengthened the manifest parsing checks and verifications.


git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@424343 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.felix.framework/src/main/java/org/apache/felix/framework/Felix.java b/org.apache.felix.framework/src/main/java/org/apache/felix/framework/Felix.java
index f371004..97e1f00 100644
--- a/org.apache.felix.framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/org.apache.felix.framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -2666,97 +2666,15 @@
     private IModule createModule(long targetId, int revision, Map headerMap)
         throws Exception
     {
-        // Get the manifest version.
-        String manifestVersion = (String) headerMap.get(FelixConstants.BUNDLE_MANIFESTVERSION);
-        manifestVersion = (manifestVersion == null) ? "1" : manifestVersion;
-        if (!manifestVersion.equals("1") && !manifestVersion.equals("2"))
+        ManifestParser mp = new ManifestParser(m_logger, headerMap);
+
+        // Verify that the bundle symbolic name and version is unique.
+        if (mp.getVersion().equals("2"))
         {
-            throw new BundleException("Unknown 'Bundle-ManifestVersion' value: " + manifestVersion);
-        }
+            String bundleVersion = mp.get(FelixConstants.BUNDLE_VERSION);
+            bundleVersion = (bundleVersion == null) ? "0.0.0" : bundleVersion;
+            String symName = (String) mp.get(FelixConstants.BUNDLE_SYMBOLICNAME);
 
-        // Create map to check for duplicate imports/exports.
-        Map dupeMap = new HashMap();
-
-        // Get export packages from bundle manifest.
-        R4Package[] pkgs = R4Package.parseImportOrExportHeader(
-            (String) headerMap.get(Constants.EXPORT_PACKAGE));
-
-        // Create non-duplicated export array.
-        dupeMap.clear();
-        for (int i = 0; i < pkgs.length; i++)
-        {
-            if (dupeMap.get(pkgs[i].getName()) == null)
-            {
-                dupeMap.put(pkgs[i].getName(), new R4Export(pkgs[i]));
-            }
-            else
-            {
-                // TODO: FRAMEWORK - Exports can be duplicated, so fix this.
-                m_logger.log(Logger.LOG_WARNING,
-                    "Duplicate export - " + pkgs[i].getName());
-            }
-        }
-        R4Export[] exports =
-            (R4Export[]) dupeMap.values().toArray(new R4Export[dupeMap.size()]);
-
-        // Get import packages from bundle manifest.
-        pkgs = R4Package.parseImportOrExportHeader(
-            (String) headerMap.get(Constants.IMPORT_PACKAGE));
-
-        // Create non-duplicated import array.
-        dupeMap.clear();
-        for (int i = 0; i < pkgs.length; i++)
-        {
-            if (dupeMap.get(pkgs[i].getName()) == null)
-            {
-                dupeMap.put(pkgs[i].getName(), new R4Import(pkgs[i]));
-            }
-            else
-            {
-                // TODO: FRAMEWORK - Determine if we should error here.
-                m_logger.log(Logger.LOG_WARNING,
-                    "Duplicate import - " + pkgs[i].getName());
-            }
-        }
-        R4Import[] imports =
-            (R4Import[]) dupeMap.values().toArray(new R4Import[dupeMap.size()]);
-
-        // Get dynamic import packages from bundle manifest.
-        pkgs = R4Package.parseImportOrExportHeader(
-            (String) headerMap.get(Constants.DYNAMICIMPORT_PACKAGE));
-
-        // Create non-duplicated dynamic import array.
-        dupeMap.clear();
-        for (int i = 0; i < pkgs.length; i++)
-        {
-            if (dupeMap.get(pkgs[i].getName()) == null)
-            {
-                dupeMap.put(pkgs[i].getName(), new R4Import(pkgs[i]));
-            }
-            else
-            {
-                // TODO: FRAMEWORK - Determine if we should error here.
-                m_logger.log(Logger.LOG_WARNING,
-                    "Duplicate import - " + pkgs[i].getName());
-            }
-        }
-        R4Import[] dynamics =
-            (R4Import[]) dupeMap.values().toArray(new R4Import[dupeMap.size()]);
-
-        // Do some validity checking on bundles with R4 headers.
-// TODO: FRAMEWORK - Perhaps these verifications and conversions can be done more efficiently.
-        if (manifestVersion.equals("2"))
-        {
-            // Verify that bundle symbolic name is specified.
-            String targetSym = (String) headerMap.get(FelixConstants.BUNDLE_SYMBOLICNAME);
-            if (targetSym == null)
-            {
-                throw new BundleException("R4 bundle manifests must include bundle symbolic name.");
-            }
-
-            // Verify that the bundle symbolic name and version is unique.
-            String targetVer = (String) headerMap.get(FelixConstants.BUNDLE_VERSION);
-            targetVer = (targetVer == null) ? "0.0.0" : targetVer;
             Bundle[] bundles = getBundles();
             for (int i = 0; (bundles != null) && (i < bundles.length); i++)
             {
@@ -2766,144 +2684,11 @@
                 String ver = (String) ((BundleImpl) bundles[i])
                     .getInfo().getCurrentHeader().get(Constants.BUNDLE_VERSION);
                 ver = (ver == null) ? "0.0.0" : ver;
-                if (targetSym.equals(sym) && targetVer.equals(ver) && (targetId != id))
+                if (symName.equals(sym) && bundleVersion.equals(ver) && (targetId != id))
                 {
                     throw new BundleException("Bundle symbolic name and version are not unique.");
                 }
             }
-
-            // Need to add symbolic name and bundle version to all R4 exports.
-            for (int i = 0; (exports != null) && (i < exports.length); i++)
-            {
-                R4Attribute[] attrs = exports[i].getAttributes();
-                R4Attribute[] newAttrs = new R4Attribute[attrs.length + 2];
-                System.arraycopy(attrs, 0, newAttrs, 0, attrs.length);
-                newAttrs[attrs.length] = new R4Attribute(
-                    Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, targetSym, false);
-                newAttrs[attrs.length + 1] = new R4Attribute(
-                    Constants.BUNDLE_VERSION_ATTRIBUTE, targetVer, false);
-                exports[i] = new R4Export(
-                    exports[i].getName(), exports[i].getDirectives(), newAttrs);
-            }
-        }
-        // Do some validity checking and conversion on bundles with R3 headers.
-        else if (manifestVersion.equals("1"))
-        {
-            // Check to make sure that R3 bundles have only specified
-            // the 'specification-version' attribute and no directives
-            // on their exports.
-            for (int i = 0; (exports != null) && (i < exports.length); i++)
-            {
-                if (exports[i].getDirectives().length != 0)
-                {
-                    throw new BundleException("R3 exports cannot contain directives.");
-                }
-                // 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 ((exports[i].getAttributes().length > 1) ||
-                    ((exports[i].getAttributes().length == 1) &&
-                        (!exports[i].getAttributes()[0].getName().equals(FelixConstants.VERSION_ATTRIBUTE))))
-                {
-                    throw new BundleException(
-                        "Export does not conform to R3 syntax: " + exports[i]);
-                }
-            }
-            
-            // Check to make sure that R3 bundles have only specified
-            // the 'specification-version' attribute and no directives
-            // on their imports.
-            for (int i = 0; (imports != null) && (i < imports.length); i++)
-            {
-                if (imports[i].getDirectives().length != 0)
-                {
-                    throw new BundleException("R3 imports cannot contain directives.");
-                }
-                // 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 ((imports[i].getVersionHigh() != null) ||
-                    (imports[i].getAttributes().length > 1) ||
-                    ((imports[i].getAttributes().length == 1) &&
-                        (!imports[i].getAttributes()[0].getName().equals(FelixConstants.VERSION_ATTRIBUTE))))
-                {
-                    throw new BundleException(
-                        "Import does not conform to R3 syntax: " + imports[i]);
-                }
-            }
-
-            // Since all R3 exports imply an import, add a corresponding
-            // import for each existing export. Create non-duplicated import array.
-            dupeMap.clear();
-            // Add existing imports.
-            for (int i = 0; i < imports.length; i++)
-            {
-                dupeMap.put(imports[i].getName(), imports[i]);
-            }
-            // Add import for each export.
-            for (int i = 0; i < exports.length; i++)
-            {
-                if (dupeMap.get(exports[i].getName()) == null)
-                {
-                    dupeMap.put(exports[i].getName(), new R4Import(exports[i]));
-                }
-            }
-            imports =
-                (R4Import[]) dupeMap.values().toArray(new R4Import[dupeMap.size()]);
-
-            // Add a "uses" directive onto each export of R3 bundles
-            // that references every other import (which will include
-            // exports, since export implies import); this is
-            // necessary since R3 bundles assumed a single class space,
-            // but R4 allows for multiple class spaces.
-            String usesValue = "";
-            for (int i = 0; (imports != null) && (i < imports.length); i++)
-            {
-                usesValue = usesValue
-                    + ((usesValue.length() > 0) ? "," : "")
-                    + imports[i].getName();
-            }
-            R4Directive uses = new R4Directive(
-                FelixConstants.USES_DIRECTIVE, usesValue);
-            for (int i = 0; (exports != null) && (i < exports.length); i++)
-            {
-                exports[i] = new R4Export(
-                    exports[i].getName(),
-                    new R4Directive[] { uses },
-                    exports[i].getAttributes());
-            }
-
-            // Check to make sure that R3 bundles have no attributes or
-            // directives on their dynamic imports.
-            for (int i = 0; (dynamics != null) && (i < dynamics.length); i++)
-            {
-                if (dynamics[i].getDirectives().length != 0)
-                {
-                    throw new BundleException("R3 dynamic imports cannot contain directives.");
-                }
-                if (dynamics[i].getAttributes().length != 0)
-                {
-                    throw new BundleException("R3 dynamic imports cannot contain attributes.");
-                }
-            }
-        }
-
-        // Get native library entry names for module library sources.
-        R4LibraryHeader[] libraryHeaders =
-            Util.parseLibraryStrings(
-                m_logger,
-                Util.parseDelimitedString(
-                    (String) headerMap.get(Constants.BUNDLE_NATIVECODE), ","));
-        R4Library[] libraries = new R4Library[libraryHeaders.length];
-        for (int i = 0; i < libraries.length; i++)
-        {
-            libraries[i] = new R4Library(
-                m_logger, m_cache, targetId, revision,
-                getProperty(Constants.FRAMEWORK_OS_NAME),
-                getProperty(Constants.FRAMEWORK_PROCESSOR),
-                libraryHeaders[i]);
         }
 
         // Now that we have all of the metadata associated with the
@@ -2916,11 +2701,18 @@
         // First, create the module.
         IModule module = m_factory.createModule(
             Long.toString(targetId) + "." + Integer.toString(revision));
+
         // Attach the R4 search policy metadata to the module.
-        m_policyCore.setExports(module, exports);
-        m_policyCore.setImports(module, imports);
-        m_policyCore.setDynamicImports(module, dynamics);
-        m_policyCore.setLibraries(module, libraries);
+        m_policyCore.setExports(module, mp.getExports());
+        m_policyCore.setImports(module, mp.getImports());
+        m_policyCore.setDynamicImports(module, mp.getDynamicImports());
+        m_policyCore.setLibraries(module,
+            mp.getLibraries(
+                m_cache,
+                targetId,
+                revision,
+                m_config.get(Constants.FRAMEWORK_OS_NAME),
+                m_config.get(Constants.FRAMEWORK_PROCESSOR)));
 
         // Create the content loader associated with the module archive.
         IContentLoader contentLoader = new ContentLoaderImpl(
diff --git a/org.apache.felix.framework/src/main/java/org/apache/felix/framework/searchpolicy/R4Package.java b/org.apache.felix.framework/src/main/java/org/apache/felix/framework/searchpolicy/R4Package.java
index 827aae2..95e51b7 100755
--- a/org.apache.felix.framework/src/main/java/org/apache/felix/framework/searchpolicy/R4Package.java
+++ b/org.apache.felix.framework/src/main/java/org/apache/felix/framework/searchpolicy/R4Package.java
@@ -16,11 +16,11 @@
  */
 package org.apache.felix.framework.searchpolicy;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
 
 import org.apache.felix.framework.util.FelixConstants;
 import org.apache.felix.framework.util.Util;
+import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
 
 public class R4Package
@@ -129,9 +129,8 @@
             }
 
             // Parse the directives/attributes.
-            R4Directive[] dirs = new R4Directive[pieces.length - pkgCount];
-            R4Attribute[] attrs = new R4Attribute[pieces.length - pkgCount];
-            int dirCount = 0, attrCount = 0;
+            Map dirsMap = new HashMap();
+            Map attrsMap = new HashMap();
             int idx = -1;
             String sep = null;
             for (int pieceIdx = pkgCount; pieceIdx < pieces.length; pieceIdx++)
@@ -165,20 +164,49 @@
                 // Save the directive/attribute in the appropriate array.
                 if (sep.equals(FelixConstants.DIRECTIVE_SEPARATOR))
                 {
-                    dirs[dirCount++] = new R4Directive(key, value);
+                    // Check for duplicates.
+                    if (dirsMap.get(key) != null)
+                    {
+                        throw new IllegalArgumentException(
+                            "Duplicate directive: " + key);
+                    }
+                    dirsMap.put(key, new R4Directive(key, value));
                 }
                 else
                 {
-                    attrs[attrCount++] = new R4Attribute(key, value, false);
+                    // Check for duplicates.
+                    if (attrsMap.get(key) != null)
+                    {
+                        throw new IllegalArgumentException(
+                            "Duplicate attribute: " + key);
+                    }
+                    attrsMap.put(key, new R4Attribute(key, value, false));
                 }
             }
 
-            // Shrink directive array.
-            R4Directive[] dirsFinal = new R4Directive[dirCount];
-            System.arraycopy(dirs, 0, dirsFinal, 0, dirCount);
-            // Shrink attribute array.
-            R4Attribute[] attrsFinal = new R4Attribute[attrCount];
-            System.arraycopy(attrs, 0, attrsFinal, 0, attrCount);
+            // Check for "version" and "specification-version" attributes
+            // and verify they are the same if both are specified.
+            R4Attribute v = (R4Attribute) attrsMap.get(Constants.VERSION_ATTRIBUTE);
+            R4Attribute sv = (R4Attribute) attrsMap.get(Constants.PACKAGE_SPECIFICATION_VERSION);
+            if ((v != null) && (sv != null))
+            {
+                // Verify they are equal.
+                if (!v.getValue().trim().equals(sv.getValue().trim()))
+                {
+                    throw new IllegalArgumentException(
+                        "Both version and specificat-version are specified, but they are not equal.");
+                }
+                // Remove spec-version since it isn't needed.
+                attrsMap.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+            }
+
+            // Create directive array.
+            R4Directive[] dirs = (R4Directive[])
+                dirsMap.values().toArray(new R4Directive[dirsMap.size()]);
+
+            // Create attribute array.
+            R4Attribute[] attrs = (R4Attribute[])
+                attrsMap.values().toArray(new R4Attribute[attrsMap.size()]);
 
             // Create package attributes for each package and
             // set directives/attributes. Add each package to
@@ -186,7 +214,7 @@
             R4Package[] pkgs = new R4Package[pkgCount];
             for (int pkgIdx = 0; pkgIdx < pkgCount; pkgIdx++)
             {
-                pkgs[pkgIdx] = new R4Package(pieces[pkgIdx], dirsFinal, attrsFinal);
+                pkgs[pkgIdx] = new R4Package(pieces[pkgIdx], dirs, attrs);
                 completeList.add(pkgs[pkgIdx]);
             }
         }
diff --git a/org.apache.felix.framework/src/main/java/org/apache/felix/framework/util/ManifestParser.java b/org.apache.felix.framework/src/main/java/org/apache/felix/framework/util/ManifestParser.java
new file mode 100644
index 0000000..d064c50
--- /dev/null
+++ b/org.apache.felix.framework/src/main/java/org/apache/felix/framework/util/ManifestParser.java
@@ -0,0 +1,342 @@
+/*
+ *   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.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.felix.framework.Logger;
+import org.apache.felix.framework.cache.BundleCache;
+import org.apache.felix.framework.searchpolicy.*;
+import org.osgi.framework.*;
+
+public class ManifestParser
+{
+    private Logger m_logger = 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;
+
+    public ManifestParser(Logger logger, Map headerMap) throws BundleException
+    {
+        m_logger = logger;
+        m_headerMap = headerMap;
+
+        // Verify that only manifest version 2 is specified.
+        String manifestVersion = get(Constants.BUNDLE_MANIFESTVERSION);
+        if ((manifestVersion != null) && !manifestVersion.equals("2"))
+        {
+            throw new BundleException(
+                "Unknown 'Bundle-ManifestVersion' value: " + manifestVersion);
+        }
+
+        // Verify bundle version syntax.
+        Version.parseVersion(get(Constants.BUNDLE_VERSION));
+
+        // Create map to check for duplicate imports/exports.
+        Map dupeMap = new HashMap();
+
+        //
+        // Parse Export-Package.
+        //
+
+        // Get export packages from bundle manifest.
+        R4Package[] pkgs = R4Package.parseImportOrExportHeader(
+            (String) headerMap.get(Constants.EXPORT_PACKAGE));
+
+        // Create non-duplicated export array.
+        dupeMap.clear();
+        for (int i = 0; i < pkgs.length; i++)
+        {
+            if (dupeMap.get(pkgs[i].getName()) == null)
+            {
+                // Verify that java.* packages are not exported.
+                if (pkgs[i].getName().startsWith("java."))
+                {
+                    throw new BundleException(
+                        "Exporting java.* packages not allowed: " + pkgs[i].getName());
+                }
+                dupeMap.put(pkgs[i].getName(), new R4Export(pkgs[i]));
+            }
+            else
+            {
+                // TODO: FRAMEWORK - Exports can be duplicated, so fix this.
+                m_logger.log(Logger.LOG_WARNING,
+                    "Duplicate export - " + pkgs[i].getName());
+            }
+        }
+        m_exports = (R4Export[]) dupeMap.values().toArray(new R4Export[dupeMap.size()]);
+
+        //
+        // Parse Import-Package.
+        //
+
+        // Get import packages from bundle manifest.
+        pkgs = R4Package.parseImportOrExportHeader(
+            (String) headerMap.get(Constants.IMPORT_PACKAGE));
+
+        // Create non-duplicated import array.
+        dupeMap.clear();
+        for (int i = 0; i < pkgs.length; i++)
+        {
+            if (dupeMap.get(pkgs[i].getName()) == null)
+            {
+                // Verify that java.* packages are not imported.
+                if (pkgs[i].getName().startsWith("java."))
+                {
+                    throw new BundleException(
+                        "Importing java.* packages not allowed: " + pkgs[i].getName());
+                }
+                dupeMap.put(pkgs[i].getName(), new R4Import(pkgs[i]));
+            }
+            else
+            {
+                throw new BundleException(
+                    "Duplicate import - " + pkgs[i].getName());
+            }
+        }
+        m_imports = (R4Import[]) dupeMap.values().toArray(new R4Import[dupeMap.size()]);
+
+        //
+        // Parse DynamicImport-Package.
+        //
+
+        // Get dynamic import packages from bundle manifest.
+        pkgs = R4Package.parseImportOrExportHeader(
+            (String) headerMap.get(Constants.DYNAMICIMPORT_PACKAGE));
+
+        // Dynamic imports can have duplicates, so just create an array.
+        m_dynamics = new R4Import[pkgs.length];
+        for (int i = 0; i < pkgs.length; i++)
+        {
+            m_dynamics[i] = new R4Import(pkgs[i]);
+        }
+
+        //
+        // Parse Bundle-NativeCode.
+        //
+
+        // Get native library entry names for module library sources.
+        m_libraryHeaders =
+            Util.parseLibraryStrings(
+                m_logger,
+                Util.parseDelimitedString(get(Constants.BUNDLE_NATIVECODE), ","));
+
+        // Do final checks and normalization of manifest.
+        if (getVersion().equals("2"))
+        {
+            checkAndNormalizeR4();
+        }
+        else
+        {
+            checkAndNormalizeR3();
+        }
+    }
+
+    public String get(String key)
+    {
+        return (String) m_headerMap.get(key);
+    }
+
+    public String getVersion()
+    {
+        String manifestVersion = get(Constants.BUNDLE_MANIFESTVERSION);
+        return (manifestVersion == null) ? "1" : manifestVersion;
+    }
+
+    public R4Export[] getExports()
+    {
+        return m_exports;
+    }
+
+    public R4Import[] getImports()
+    {
+        return m_imports;
+    }
+
+    public R4Import[] getDynamicImports()
+    {
+        return m_dynamics;
+    }
+
+    public R4LibraryHeader[] getLibraryHeaders()
+    {
+        return m_libraryHeaders;
+    }
+
+    public R4Library[] getLibraries(
+        BundleCache cache, long id, int revision, String osName, String processor)
+    {
+        R4Library[] libraries = new R4Library[m_libraryHeaders.length];
+        for (int i = 0; i < libraries.length; i++)
+        {
+            libraries[i] = new R4Library(
+                m_logger, cache, id, revision, osName, processor, m_libraryHeaders[i]);
+        }
+        return libraries;
+    }
+
+    private void checkAndNormalizeR3() throws BundleException
+    {
+        // Check to make sure that R3 bundles have only specified
+        // the 'specification-version' attribute and no directives
+        // on their exports.
+        for (int i = 0; (m_exports != null) && (i < m_exports.length); i++)
+        {
+            if (m_exports[i].getDirectives().length != 0)
+            {
+                throw new BundleException("R3 exports cannot contain directives.");
+            }
+            // 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 ((m_exports[i].getAttributes().length > 1) ||
+                ((m_exports[i].getAttributes().length == 1) &&
+                    (!m_exports[i].getAttributes()[0].getName().equals(Constants.VERSION_ATTRIBUTE))))
+            {
+                throw new BundleException(
+                    "Export does not conform to R3 syntax: " + m_exports[i]);
+            }
+        }
+        
+        // Check to make sure that R3 bundles have only specified
+        // the 'specification-version' attribute and no directives
+        // on their imports.
+        for (int i = 0; (m_imports != null) && (i < m_imports.length); i++)
+        {
+            if (m_imports[i].getDirectives().length != 0)
+            {
+                throw new BundleException("R3 imports cannot contain directives.");
+            }
+            // 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 ((m_imports[i].getVersionHigh() != null) ||
+                (m_imports[i].getAttributes().length > 1) ||
+                ((m_imports[i].getAttributes().length == 1) &&
+                    (!m_imports[i].getAttributes()[0].getName().equals(Constants.VERSION_ATTRIBUTE))))
+            {
+                throw new BundleException(
+                    "Import does not conform to R3 syntax: " + m_imports[i]);
+            }
+        }
+
+        // Since all R3 exports imply an import, add a corresponding
+        // import for each existing export. Create non-duplicated import array.
+        Map map =  new HashMap();
+        // Add existing imports.
+        for (int i = 0; i < m_imports.length; i++)
+        {
+            map.put(m_imports[i].getName(), m_imports[i]);
+        }
+        // Add import for each export.
+        for (int i = 0; i < m_exports.length; i++)
+        {
+            if (map.get(m_exports[i].getName()) == null)
+            {
+                map.put(m_exports[i].getName(), new R4Import(m_exports[i]));
+            }
+        }
+        m_imports =
+            (R4Import[]) map.values().toArray(new R4Import[map.size()]);
+
+        // Add a "uses" directive onto each export of R3 bundles
+        // that references every other import (which will include
+        // exports, since export implies import); this is
+        // necessary since R3 bundles assumed a single class space,
+        // but R4 allows for multiple class spaces.
+        String usesValue = "";
+        for (int i = 0; (m_imports != null) && (i < m_imports.length); i++)
+        {
+            usesValue = usesValue
+                + ((usesValue.length() > 0) ? "," : "")
+                + m_imports[i].getName();
+        }
+        R4Directive uses = new R4Directive(
+            Constants.USES_DIRECTIVE, usesValue);
+        for (int i = 0; (m_exports != null) && (i < m_exports.length); i++)
+        {
+            m_exports[i] = new R4Export(
+                m_exports[i].getName(),
+                new R4Directive[] { uses },
+                m_exports[i].getAttributes());
+        }
+
+        // Check to make sure that R3 bundles have no attributes or
+        // directives on their dynamic imports.
+        for (int i = 0; (m_dynamics != null) && (i < m_dynamics.length); i++)
+        {
+            if (m_dynamics[i].getDirectives().length != 0)
+            {
+                throw new BundleException("R3 dynamic imports cannot contain directives.");
+            }
+            if (m_dynamics[i].getAttributes().length != 0)
+            {
+                throw new BundleException("R3 dynamic imports cannot contain attributes.");
+            }
+        }
+    }
+
+    private void checkAndNormalizeR4() throws BundleException
+    {
+        // Verify that bundle symbolic name is specified.
+        String symName = get(Constants.BUNDLE_SYMBOLICNAME);
+        if (symName == null)
+        {
+            throw new BundleException("R4 bundle manifests must include bundle symbolic name.");
+        }
+
+        // Verify that there are no duplicate directives.
+        Map map = new HashMap();
+        for (int i = 0; (m_exports != null) && (i < m_exports.length); i++)
+        {
+            String targetVer = get(Constants.BUNDLE_VERSION);
+            targetVer = (targetVer == null) ? "0.0.0" : targetVer;
+
+            R4Attribute[] attrs = m_exports[i].getAttributes();
+            R4Attribute[] newAttrs = new R4Attribute[attrs.length + 2];
+            System.arraycopy(attrs, 0, newAttrs, 0, attrs.length);
+            newAttrs[attrs.length] = new R4Attribute(
+                Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, symName, false);
+            newAttrs[attrs.length + 1] = new R4Attribute(
+                Constants.BUNDLE_VERSION_ATTRIBUTE, targetVer, false);
+            m_exports[i] = new R4Export(
+                m_exports[i].getName(), m_exports[i].getDirectives(), newAttrs);
+        }
+
+        // Need to add symbolic name and bundle version to all R4 exports.
+        for (int i = 0; (m_exports != null) && (i < m_exports.length); i++)
+        {
+            String targetVer = get(Constants.BUNDLE_VERSION);
+            targetVer = (targetVer == null) ? "0.0.0" : targetVer;
+
+            R4Attribute[] attrs = m_exports[i].getAttributes();
+            R4Attribute[] newAttrs = new R4Attribute[attrs.length + 2];
+            System.arraycopy(attrs, 0, newAttrs, 0, attrs.length);
+            newAttrs[attrs.length] = new R4Attribute(
+                Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, symName, false);
+            newAttrs[attrs.length + 1] = new R4Attribute(
+                Constants.BUNDLE_VERSION_ATTRIBUTE, targetVer, false);
+            m_exports[i] = new R4Export(
+                m_exports[i].getName(), m_exports[i].getDirectives(), newAttrs);
+        }
+    }
+}
\ No newline at end of file