Implement custom manifest parsing to avoid using JarFiles, since they
appear to consume a lot of memory. (FELIX-2721)


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1052040 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java b/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java
index 5085fd6..3b5512a 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java
@@ -32,7 +32,7 @@
 import java.util.zip.ZipEntry;
 import org.apache.felix.framework.Logger;
 import org.apache.felix.framework.util.FelixConstants;
-import org.apache.felix.framework.util.JarFileX;
+import org.apache.felix.framework.util.ZipFileX;
 import org.apache.felix.framework.util.Util;
 import org.apache.felix.framework.resolver.Content;
 import org.osgi.framework.Constants;
@@ -48,20 +48,20 @@
     private final Object m_revisionLock;
     private final File m_rootDir;
     private final File m_file;
-    private final JarFileX m_jarFile;
-    private final boolean m_isJarFileOwner;
+    private final ZipFileX m_zipFile;
+    private final boolean m_isZipFileOwner;
     private Map m_nativeLibMap;
 
     public JarContent(Logger logger, Map configMap, Object revisionLock, File rootDir,
-        File file, JarFileX jarFile)
+        File file, ZipFileX zipFile)
     {
         m_logger = logger;
         m_configMap = configMap;
         m_revisionLock = revisionLock;
         m_rootDir = rootDir;
         m_file = file;
-        m_jarFile = (jarFile == null) ? openJarFile(m_file) : jarFile;
-        m_isJarFileOwner = (jarFile == null);
+        m_zipFile = (zipFile == null) ? openZipFile(m_file) : zipFile;
+        m_isZipFileOwner = (zipFile == null);
     }
 
     protected void finalize()
@@ -73,9 +73,9 @@
     {
         try
         {
-            if (m_isJarFileOwner)
+            if (m_isZipFileOwner)
             {
-                m_jarFile.close();
+                m_zipFile.close();
             }
         }
         catch (Exception ex)
@@ -90,7 +90,7 @@
     {
         try
         {
-            ZipEntry ze = m_jarFile.getEntry(name);
+            ZipEntry ze = m_zipFile.getEntry(name);
             return ze != null;
         }
         catch (Exception ex)
@@ -105,7 +105,7 @@
     public Enumeration getEntries()
     {
         // Wrap entries enumeration to filter non-matching entries.
-        Enumeration e = new EntriesEnumeration(m_jarFile.entries());
+        Enumeration e = new EntriesEnumeration(m_zipFile.entries());
 
         // Spec says to return null if there are no entries.
         return (e.hasMoreElements()) ? e : null;
@@ -119,12 +119,12 @@
 
         try
         {
-            ZipEntry ze = m_jarFile.getEntry(name);
+            ZipEntry ze = m_zipFile.getEntry(name);
             if (ze == null)
             {
                 return null;
             }
-            is = m_jarFile.getInputStream(ze);
+            is = m_zipFile.getInputStream(ze);
             if (is == null)
             {
                 return null;
@@ -173,12 +173,12 @@
 
         try
         {
-            ZipEntry ze = m_jarFile.getEntry(name);
+            ZipEntry ze = m_zipFile.getEntry(name);
             if (ze == null)
             {
                 return null;
             }
-            is = m_jarFile.getInputStream(ze);
+            is = m_zipFile.getInputStream(ze);
             if (is == null)
             {
                 return null;
@@ -211,7 +211,7 @@
         if (entryName.equals(FelixConstants.CLASS_PATH_DOT))
         {
             return new JarContent(m_logger, m_configMap, m_revisionLock,
-                m_rootDir, m_file, m_jarFile);
+                m_rootDir, m_file, m_zipFile);
         }
 
         // Remove any leading slash.
@@ -228,7 +228,7 @@
         // Determine if the entry is an emdedded JAR file or
         // directory in the bundle JAR file. Ignore any entries
         // that do not exist per the spec.
-        ZipEntry ze = m_jarFile.getEntry(entryName);
+        ZipEntry ze = m_zipFile.getEntry(entryName);
         if ((ze != null) && ze.isDirectory())
         {
             File extractDir = new File(embedDir, entryName);
@@ -297,7 +297,7 @@
 
         // The entry name must refer to a file type, since it is
         // a native library, not a directory.
-        ZipEntry ze = m_jarFile.getEntry(entryName);
+        ZipEntry ze = m_zipFile.getEntry(entryName);
         if ((ze != null) && !ze.isDirectory())
         {
             // Extracting the embedded native library file impacts all other
@@ -336,7 +336,7 @@
                         try
                         {
                             is = new BufferedInputStream(
-                                m_jarFile.getInputStream(ze),
+                                m_zipFile.getInputStream(ze),
                                 BundleCache.BUFSIZE);
                             if (is == null)
                             {
@@ -440,7 +440,7 @@
             try
             {
                 // Make sure class path entry is a JAR file.
-                ZipEntry ze = m_jarFile.getEntry(jarPath);
+                ZipEntry ze = m_zipFile.getEntry(jarPath);
                 if (ze == null)
                 {
                     return;
@@ -462,7 +462,7 @@
                     }
 
                     // Extract embedded JAR into its directory.
-                    is = new BufferedInputStream(m_jarFile.getInputStream(ze), BundleCache.BUFSIZE);
+                    is = new BufferedInputStream(m_zipFile.getInputStream(ze), BundleCache.BUFSIZE);
                     if (is == null)
                     {
                         throw new IOException("No input stream: " + jarPath);
@@ -478,11 +478,11 @@
         }
     }
 
-    private static JarFileX openJarFile(File file) throws RuntimeException
+    private static ZipFileX openZipFile(File file) throws RuntimeException
     {
         try
         {
-            return BundleCache.getSecureAction().openJAR(file, false);
+            return BundleCache.getSecureAction().openZipFile(file);
         }
         catch (IOException ex)
         {
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java b/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java
index 9e69f6f..ccd0109 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java
@@ -18,6 +18,7 @@
  */
 package org.apache.felix.framework.cache;
 
+import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -26,10 +27,11 @@
 import java.net.URLConnection;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.jar.Manifest;
+import java.util.Map.Entry;
+import java.util.zip.ZipEntry;
 
 import org.apache.felix.framework.Logger;
-import org.apache.felix.framework.util.JarFileX;
+import org.apache.felix.framework.util.ZipFileX;
 import org.apache.felix.framework.util.StringMap;
 import org.apache.felix.framework.util.Util;
 import org.apache.felix.framework.resolver.Content;
@@ -50,7 +52,7 @@
     private static final transient String BUNDLE_JAR_FILE = "bundle.jar";
 
     private File m_bundleFile = null;
-    private final JarFileX m_jarFile;
+    private final ZipFileX m_zipFile;
 
     public JarRevision(
         Logger logger, Map configMap, File revisionRootDir,
@@ -82,21 +84,21 @@
         initialize(byReference, is);
 
         // Open shared copy of the JAR file.
-        JarFileX jarFile = null;
+        ZipFileX zipFile = null;
         try
         {
             // Open bundle JAR file.
-            jarFile = BundleCache.getSecureAction().openJAR(m_bundleFile, false);
+            zipFile = BundleCache.getSecureAction().openZipFile(m_bundleFile);
             // Error if no jar file.
-            if (jarFile == null)
+            if (zipFile == null)
             {
                 throw new IOException("No JAR file found.");
             }
-            m_jarFile = jarFile;
+            m_zipFile = zipFile;
         }
         catch (Exception ex)
         {
-            if (jarFile != null) jarFile.close();
+            if (zipFile != null) zipFile.close();
             throw ex;
         }
     }
@@ -104,9 +106,9 @@
     public Map getManifestHeader() throws Exception
     {
         // Get the embedded resource.
-        Manifest mf = m_jarFile.getManifest();
+        Map headers = getMainAttributes(m_zipFile);
         // Use an empty map if there is no manifest.
-        Map headers = (mf == null) ? new HashMap() : mf.getMainAttributes();
+        headers = (headers == null) ? new HashMap() : headers;
         // Create a case insensitive map of manifest attributes.
         return new StringMap(headers, false);
     }
@@ -114,12 +116,12 @@
     public synchronized Content getContent() throws Exception
     {
         return new JarContent(getLogger(), getConfig(), this, getRevisionRootDir(),
-            m_bundleFile, m_jarFile);
+            m_bundleFile, m_zipFile);
     }
 
     protected void close() throws Exception
     {
-        m_jarFile.close();
+        m_zipFile.close();
     }
 
     //
@@ -192,4 +194,134 @@
             if (is != null) is.close();
         }
     }
+
+    public static int readLine(InputStream is, byte[] buf) throws IOException
+    {
+        for (int i = 0; i < buf.length; i++)
+        {
+            int b = is.read();
+            if (b < 0)
+            {
+                return (i == 0) ? -1 : i;
+            }
+            else
+            {
+                buf[i] = (byte) b;
+                if (buf[i] == '\n')
+                {
+                    return i + 1;
+                }
+            }
+        }
+        return 0;
+    }
+
+    private static Map<String, String> getMainAttributes(ZipFileX zipFile) throws IOException
+    {
+        Map<String, String> mainAttrs = new HashMap<String, String>();
+        Map<String, byte[]> tmpMap = new HashMap<String, byte[]>();
+
+        byte[] buf = new byte[512];
+        ZipEntry ze = zipFile.getEntry("META-INF/MANIFEST.MF");
+        InputStream is = new BufferedInputStream(zipFile.getInputStream(ze));
+        String lastName = null;
+        try
+        {
+            for (int len = readLine(is, buf); len != -1; len = readLine(is, buf))
+            {
+                // Make sure line ends with a line feed.
+                if (buf[len - 1] != '\n')
+                {
+                    throw new IOException(
+                        "Manifest error: Line either too long or no line feed - "
+                        + new String(buf, 0, 0, len));
+                }
+
+                // Ignore line feed.
+                len--;
+
+                // If line ends with carriage return, ignore it.
+                if ((len > 0) && (buf[len - 1] == '\r'))
+                {
+                    len--;
+                }
+
+                // If line is empty, then we've reached the end
+                // of the main attributes group.
+                if (len == 0)
+                {
+                    break;
+                }
+
+                // Check if this is a continuation line. If so, read the
+                // entire line and add it to the previous line value.
+                if (buf[0] == ' ')
+                {
+                    if (lastName == null)
+                    {
+                        throw new IOException(
+                            "Manifest syntax: Invalid line continuation - "
+                            + new String(buf, 0, 0, len));
+                    }
+                    byte[] lastValue = tmpMap.get(lastName);
+                    byte[] tmp = new byte[lastValue.length + len - 1];
+                    System.arraycopy(lastValue, 0, tmp, 0, lastValue.length);
+                    System.arraycopy(buf, 1, tmp, lastValue.length, len - 1);
+                    tmpMap.put(lastName, tmp);
+                }
+                // Otherwise, try to find the attribute name and its value.
+                else
+                {
+                    for (int i = 0; i < len; i++)
+                    {
+                        // If we are at the end, then this must be an error.
+                        if (i == (len - 1))
+                        {
+                            throw new IOException(
+                                "Manifest syntax: Invalid attribute name - "
+                                + new String(buf, 0, 0, len));
+                        }
+                        // We found the end of the attribute name
+                        else if (buf[i] == ':')
+                        {
+                            // Make sure the header has a space separator.
+                            if (buf[i + 1] != ' ')
+                            {
+                                throw new IOException(
+                                    "Manifest syntax: Header space separator missing - "
+                                    + new String(buf, 0, 0, len));
+                            }
+                            // Convert attribute name to a string.
+                            lastName = new String(buf, 0, 0, i);
+                            byte[] tmp = new byte[len - i - 2];
+                            System.arraycopy(buf, i + 2, tmp, 0, len - i - 2);
+                            byte[] old = tmpMap.put(lastName, tmp);
+                            if (old != null)
+                            {
+                                throw new IllegalArgumentException(
+                                    "Manifest syntax: Duplicate header - "
+                                    + new String(buf, 0, 0, len));
+                            }
+                            break;
+                        }
+                    }
+                }
+            }
+
+            for (Entry<String, byte[]> entry : tmpMap.entrySet())
+            {
+                byte[] value = entry.getValue();
+                mainAttrs.put(
+                    entry.getKey(),
+                    new String(value, 0, value.length, "UTF8"));
+            }
+
+        }
+        finally
+        {
+            is.close();
+        }
+
+        return mainAttrs;
+    }
 }
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java b/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java
index 1410f0f..b806210 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java
@@ -583,15 +583,15 @@
         }
     }
 
-    public JarFileX openJAR(File file) throws IOException
+    public ZipFileX openZipFile(File file) throws IOException
     {
         if (System.getSecurityManager() != null)
         {
             try
             {
                 Actions actions = (Actions) m_actions.get();
-                actions.set(Actions.OPEN_JARX_ACTION, file);
-                return (JarFileX) AccessController.doPrivileged(actions, m_acc);
+                actions.set(Actions.OPEN_ZIPFILE_ACTION, file);
+                return (ZipFileX) AccessController.doPrivileged(actions, m_acc);
             }
             catch (PrivilegedActionException ex)
             {
@@ -604,32 +604,7 @@
         }
         else
         {
-            return new JarFileX(file);
-        }
-    }
-
-    public JarFileX openJAR(File file, boolean verify) throws IOException
-    {
-        if (System.getSecurityManager() != null)
-        {
-            try
-            {
-                Actions actions = (Actions) m_actions.get();
-                actions.set(Actions.OPEN_JARX_VERIFY_ACTION, file, (verify ? Boolean.TRUE : Boolean.FALSE));
-                return (JarFileX) AccessController.doPrivileged(actions, m_acc);
-            }
-            catch (PrivilegedActionException ex)
-            {
-                if (ex.getException() instanceof IOException)
-                {
-                    throw (IOException) ex.getException();
-                }
-                throw (RuntimeException) ex.getException();
-            }
-        }
-        else
-        {
-            return new JarFileX(file, verify);
+            return new ZipFileX(file);
         }
     }
 
@@ -1097,17 +1072,16 @@
         public static final int LIST_DIRECTORY_ACTION = 27;
         public static final int MAKE_DIRECTORIES_ACTION = 28;
         public static final int MAKE_DIRECTORY_ACTION = 29;
-        public static final int OPEN_JARX_ACTION = 30;
-        public static final int OPEN_JARX_VERIFY_ACTION = 31;
-        public static final int OPEN_URLCONNECTION_ACTION = 32;
-        public static final int RENAME_FILE_ACTION = 33;
-        public static final int SET_ACCESSIBLE_ACTION = 34;
-        public static final int START_ACTIVATOR_ACTION = 35;
-        public static final int STOP_ACTIVATOR_ACTION = 36;
-        public static final int SWAP_FIELD_ACTION = 37;
-        public static final int SYSTEM_EXIT_ACTION = 38;
-        public static final int FLUSH_FIELD_ACTION = 39;
-        public static final int GET_CLASS_LOADER_ACTION = 40;
+        public static final int OPEN_ZIPFILE_ACTION = 30;
+        public static final int OPEN_URLCONNECTION_ACTION = 31;
+        public static final int RENAME_FILE_ACTION = 32;
+        public static final int SET_ACCESSIBLE_ACTION = 33;
+        public static final int START_ACTIVATOR_ACTION = 34;
+        public static final int STOP_ACTIVATOR_ACTION = 35;
+        public static final int SWAP_FIELD_ACTION = 36;
+        public static final int SYSTEM_EXIT_ACTION = 37;
+        public static final int FLUSH_FIELD_ACTION = 38;
+        public static final int GET_CLASS_LOADER_ACTION = 39;
 
         private int m_action = -1;
         private Object m_arg1 = null;
@@ -1256,10 +1230,8 @@
                     return ((File) arg1).mkdirs() ? Boolean.TRUE : Boolean.FALSE;
                 case MAKE_DIRECTORY_ACTION:
                     return ((File) arg1).mkdir() ? Boolean.TRUE : Boolean.FALSE;
-                case OPEN_JARX_ACTION:
-                    return new JarFileX((File) arg1);
-                case OPEN_JARX_VERIFY_ACTION:
-                    return new JarFileX((File) arg1, ((Boolean) arg2).booleanValue());
+                case OPEN_ZIPFILE_ACTION:
+                    return new ZipFileX((File) arg1);
                 case OPEN_URLCONNECTION_ACTION:
                     return ((URL) arg1).openConnection();
                 case RENAME_FILE_ACTION:
diff --git a/framework/src/main/java/org/apache/felix/framework/util/JarFileX.java b/framework/src/main/java/org/apache/felix/framework/util/ZipFileX.java
similarity index 64%
rename from framework/src/main/java/org/apache/felix/framework/util/JarFileX.java
rename to framework/src/main/java/org/apache/felix/framework/util/ZipFileX.java
index 5174529..550a680 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/JarFileX.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/ZipFileX.java
@@ -21,41 +21,32 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
 import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 
 /**
  * The purpose of this class is to fix an apparent bug in the JVM in versions
  * 1.4.2 and lower where directory entries in ZIP/JAR files are not correctly
  * identified.
 **/
-public class JarFileX extends JarFile
+public class ZipFileX extends ZipFile
 {
-    public JarFileX(File file) throws IOException
+    public ZipFileX(File file) throws IOException
     {
         super(file);
     }
 
-    public JarFileX(File file, boolean verify) throws IOException
+
+    public ZipFileX(File file, int mode) throws IOException
     {
-        super(file, verify);
+        super(file, mode);
     }
 
-    public JarFileX(File file, boolean verify, int mode) throws IOException
-    {
-        super(file, verify, mode);
-    }
-
-    public JarFileX(String name) throws IOException
+    public ZipFileX(String name) throws IOException
     {
         super(name);
     }
 
-    public JarFileX(String name, boolean verify) throws IOException
-    {
-        super(name, verify);
-    }
-
     public ZipEntry getEntry(String name)
     {
         ZipEntry entry = super.getEntry(name);
@@ -69,18 +60,4 @@
         }
         return entry;
     }
-
-    public JarEntry getJarEntry(String name)
-    {
-        JarEntry entry = super.getJarEntry(name);
-        if ((entry != null) && (entry.getSize() == 0) && !entry.isDirectory())
-        {
-            JarEntry dirEntry = super.getJarEntry(name + '/');
-            if (dirEntry != null)
-            {
-                entry = dirEntry;
-            }
-        }
-        return entry;
-    }
 }
\ No newline at end of file