Author: pauls Date: Wed Mar 14 13:28:58 2018 New Revision: 1826714 URL: http://svn.apache.org/viewvc?rev=1826714&view=rev Log: FELIX-5808: Clean-up bundlecache manifest parsing and entry reading
Modified: felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java Modified: felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java?rev=1826714&r1=1826713&r2=1826714&view=diff ============================================================================== --- felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java (original) +++ felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java Wed Mar 14 13:28:58 2018 @@ -18,16 +18,23 @@ */ package org.apache.felix.framework.cache; -import java.io.*; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.util.*; - import org.apache.felix.framework.Logger; import org.apache.felix.framework.util.SecureAction; import org.apache.felix.framework.util.WeakZipFileFactory; import org.osgi.framework.Constants; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.ref.SoftReference; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + /** * <p> * This class, combined with <tt>BundleArchive</tt>, and concrete @@ -83,8 +90,8 @@ public class BundleCache // TODO: CACHE - This should eventually be removed along with the code // supporting the old multi-file bundle cache format. public static final String CACHE_SINGLEBUNDLEFILE_PROP = "felix.cache.singlebundlefile"; - - protected static transient int BUFSIZE = 4096; + private static final ThreadLocal m_defaultBuffer = new ThreadLocal(); + private static volatile int DEFAULT_BUFFER = 1024 * 64; private static transient final String CACHE_DIR_NAME = "felix-cache"; private static transient final String CACHE_ROOTDIR_DEFAULT = "."; @@ -182,6 +189,187 @@ public class BundleCache } } + // Parse the main attributes of the manifest of the given jarfile. + // The idea is to not open the jar file as a java.util.jarfile but + // read the mainfest from the zipfile directly and parse it manually + // to use less memory and be faster. + // + // @return the given map for convenience + public static Map<String, Object> getMainAttributes(Map<String, Object> headers, InputStream inputStream, long size) throws Exception + { + if (size > 0) + { + return getMainAttributes(headers, inputStream, (int) (size < Integer.MAX_VALUE ? size : Integer.MAX_VALUE)); + } + else + { + return headers; + } + } + + static byte[] read(InputStream input, long size) throws Exception + { + return read(input, size <= Integer.MAX_VALUE ? (int) size : Integer.MAX_VALUE); + } + + static byte[] read(InputStream input, int size) throws Exception + { + if (size <= 0) + { + return new byte[0]; + } + + byte[] result = new byte[size]; + + Exception exception = null; + try + { + for (int i = input.read(result, 0, size); i != -1 && i < size; i += input.read(result, i, size - i)) + { + + } + } + catch (Exception ex) + { + exception = ex; + } + finally + { + try + { + input.close(); + } + catch (Exception ex) + { + throw exception != null ? exception : ex; + } + } + + return result; + } + + public static Map<String, Object> getMainAttributes(Map<String, Object> headers, InputStream inputStream, int size) throws Exception + { + if (size <= 0) + { + inputStream.close(); + return headers; + } + + // Get the buffer for this thread if there is one already otherwise, + // create one of size DEFAULT_BUFFER (64K) if the manifest is less + // than 64k or of the size of the manifest. + SoftReference ref = (SoftReference) m_defaultBuffer.get(); + byte[] bytes = null; + if (ref != null) + { + bytes = (byte[]) ref.get(); + } + + if (bytes == null) + { + bytes = new byte[size + 1 > DEFAULT_BUFFER ? size + 1 : DEFAULT_BUFFER]; + m_defaultBuffer.set(new SoftReference(bytes)); + } + else if (size + 1 > bytes.length) + { + bytes = new byte[size + 1]; + m_defaultBuffer.set(new SoftReference(bytes)); + } + + // Now read in the manifest in one go into the bytes array. + // The InputStream should be already buffered and can handle up to 64K buffers in one go. + try + { + int i = inputStream.read(bytes); + while (i < size) + { + i += inputStream.read(bytes, i, bytes.length - i); + } + } + finally + { + inputStream.close(); + } + + // Force a new line at the end of the manifest to deal with broken manifest without any line-ending + bytes[size++] = '\n'; + + // Now parse the main attributes. The idea is to do that + // without creating new byte arrays. Therefore, we read through + // the manifest bytes inside the bytes array and write them back into + // the same array unless we don't need them (e.g., \r\n and \n are skipped). + // That allows us to create the strings from the bytes array without the skipped + // chars. We stop as soon as we see a blank line as that denotes that the main + // attributes part is finished. + String key = null; + int last = 0; + int current = 0; + for (int i = 0; i < size; i++) + { + // skip \r and \n if it is followed by another \n + // (we catch the blank line case in the next iteration) + if (bytes[i] == '\r') + { + if ((i + 1 < size) && (bytes[i + 1] == '\n')) + { + continue; + } + } + if (bytes[i] == '\n') + { + if ((i + 1 < size) && (bytes[i + 1] == ' ')) + { + i++; + continue; + } + } + // If we don't have a key yet and see the first : we parse it as the key + // and skip the :<blank> that follows it. + if ((key == null) && (bytes[i] == ':')) + { + key = new String(bytes, last, (current - last), "UTF-8"); + if ((i + 1 < size) && (bytes[i + 1] == ' ')) + { + last = current + 1; + continue; + } + else + { + throw new Exception("Manifest error: Missing space separator - " + key); + } + } + // if we are at the end of a line + if (bytes[i] == '\n') + { + // and it is a blank line stop parsing (main attributes are done) + if ((last == current) && (key == null)) + { + break; + } + // Otherwise, parse the value and add it to the map (we throw an + // exception if we don't have a key or the key already exist. + String value = new String(bytes, last, (current - last), "UTF-8"); + if (key == null) + { + throw new Exception("Manifest error: Missing attribute name - " + value); + } + else if (headers.put(key.intern(), value) != null) + { + throw new Exception("Manifest error: Duplicate attribute name - " + key); + } + last = current; + key = null; + } + else + { + // write back the byte if it needs to be included in the key or the value. + bytes[current++] = bytes[i]; + } + } + return headers; + } + public synchronized void release() { if (m_lock != null) @@ -222,7 +410,7 @@ public class BundleCache String sBufSize = (String) m_configMap.get(CACHE_BUFSIZE_PROP); if (sBufSize != null) { - BUFSIZE = Integer.parseInt(sBufSize); + DEFAULT_BUFFER = Integer.parseInt(sBufSize); } } catch (NumberFormatException ne) @@ -346,23 +534,41 @@ public class BundleCache static void copyStreamToFile(InputStream is, File outputFile) throws IOException { + // Get the buffer for this thread if there is one already otherwise, + // create one of size DEFAULT_BUFFER + SoftReference ref = (SoftReference) m_defaultBuffer.get(); + byte[] bytes = null; + if (ref != null) + { + bytes = (byte[]) ref.get(); + } + + if (bytes == null) + { + bytes = new byte[DEFAULT_BUFFER]; + m_defaultBuffer.set(new SoftReference(bytes)); + } + OutputStream os = null; try { os = getSecureAction().getFileOutputStream(outputFile); - os = new BufferedOutputStream(os, BUFSIZE); - byte[] b = new byte[BUFSIZE]; - int len = 0; - while ((len = is.read(b)) != -1) + for (int i = is.read(bytes);i != -1; i = is.read(bytes)) { - os.write(b, 0, len); + os.write(bytes, 0, i); } } finally { - if (is != null) is.close(); - if (os != null) os.close(); + try + { + if (is != null) is.close(); + } + finally + { + if (os != null) os.close(); + } } } Modified: felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java?rev=1826714&r1=1826713&r2=1826714&view=diff ============================================================================== --- felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java (original) +++ felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java Wed Mar 14 13:28:58 2018 @@ -18,16 +18,23 @@ */ package org.apache.felix.framework.cache; -import java.io.*; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.*; import org.apache.felix.framework.Logger; import org.apache.felix.framework.util.FelixConstants; import org.apache.felix.framework.util.Util; import org.apache.felix.framework.util.WeakZipFileFactory; import org.osgi.framework.Constants; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Properties; + public class DirectoryContent implements Content { private static final int BUFSIZE = 4096; @@ -96,44 +103,20 @@ public class DirectoryContent implements } // Get the embedded resource. - InputStream is = null; - ByteArrayOutputStream baos = null; + File file = new File(m_dir, name); try { - is = new BufferedInputStream( - BundleCache.getSecureAction().getFileInputStream(new File(m_dir, name))); - baos = new ByteArrayOutputStream(BUFSIZE); - byte[] buf = new byte[BUFSIZE]; - int n = 0; - while ((n = is.read(buf, 0, buf.length)) >= 0) - { - baos.write(buf, 0, n); - } - return baos.toByteArray(); + return BundleCache.read(BundleCache.getSecureAction().getFileInputStream(file), file.length()); } catch (Exception ex) { + m_logger.log( + Logger.LOG_ERROR, + "DirectoryContent: Unable to read bytes for file " + name + " from file " + file.getAbsolutePath(), ex); return null; } - finally - { - try - { - if (baos != null) baos.close(); - } - catch (Exception ex) - { - } - try - { - if (is != null) is.close(); - } - catch (Exception ex) - { - } - } } public InputStream getEntryAsStream(String name) @@ -144,7 +127,18 @@ public class DirectoryContent implements name = name.substring(1); } - return BundleCache.getSecureAction().getFileInputStream(new File(m_dir, name)); + File file = new File(m_dir, name); + try + { + return BundleCache.getSecureAction().getFileInputStream(file); + } + catch (Exception ex) + { + m_logger.log( + Logger.LOG_ERROR, + "DirectoryContent: Unable to create inputstream for file " + name + " from file " + file.getAbsolutePath(), ex); + return null; + } } public URL getEntryAsURL(String name) @@ -272,13 +266,7 @@ public class DirectoryContent implements try { - is = new BufferedInputStream( - BundleCache.getSecureAction().getFileInputStream(entryFile), - BundleCache.BUFSIZE); - if (is == null) - { - throw new IOException("No input stream: " + entryName); - } + is = BundleCache.getSecureAction().getFileInputStream(entryFile); // Create the file. BundleCache.copyStreamToFile(is, libFile); @@ -305,17 +293,6 @@ public class DirectoryContent implements Logger.LOG_ERROR, "Extracting native library.", ex); } - finally - { - try - { - if (is != null) is.close(); - } - catch (IOException ex) - { - // Not much we can do. - } - } } } else Modified: felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java?rev=1826714&r1=1826713&r2=1826714&view=diff ============================================================================== --- felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java (original) +++ felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java Wed Mar 14 13:28:58 2018 @@ -18,16 +18,14 @@ */ package org.apache.felix.framework.cache; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; -import java.util.jar.Manifest; - import org.apache.felix.framework.Logger; import org.apache.felix.framework.util.StringMap; import org.apache.felix.framework.util.WeakZipFileFactory; +import java.io.File; +import java.io.IOException; +import java.util.Map; + /** * <p> * This class implements a bundle archive revision for exploded bundle @@ -72,29 +70,8 @@ class DirectoryRevision extends BundleAr public synchronized Map<String, Object> getManifestHeader() throws Exception { - // Read the header file from the reference directory. - InputStream is = null; - - try - { - // Open manifest file. - is = BundleCache.getSecureAction() - .getFileInputStream(new File(m_refDir, "META-INF/MANIFEST.MF")); - // Error if no jar file. - if (is == null) - { - throw new IOException("No manifest file found."); - } - - // Get manifest. - Manifest mf = new Manifest(is); - // Create a case insensitive map of manifest attributes. - return new StringMap(mf.getMainAttributes()); - } - finally - { - if (is != null) is.close(); - } + File manifest = new File(m_refDir, "META-INF/MANIFEST.MF"); + return manifest.isFile() ? BundleCache.getMainAttributes(new StringMap(), BundleCache.getSecureAction().getFileInputStream(manifest), manifest.length()) : null; } public synchronized Content getContent() throws Exception Modified: felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java?rev=1826714&r1=1826713&r2=1826714&view=diff ============================================================================== --- felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java (original) +++ felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java Wed Mar 14 13:28:58 2018 @@ -18,8 +18,13 @@ */ package org.apache.felix.framework.cache; -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; +import org.apache.felix.framework.Logger; +import org.apache.felix.framework.util.FelixConstants; +import org.apache.felix.framework.util.Util; +import org.apache.felix.framework.util.WeakZipFileFactory; +import org.apache.felix.framework.util.WeakZipFileFactory.WeakZipFile; +import org.osgi.framework.Constants; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -30,16 +35,9 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.zip.ZipEntry; -import org.apache.felix.framework.Logger; -import org.apache.felix.framework.util.FelixConstants; -import org.apache.felix.framework.util.Util; -import org.apache.felix.framework.util.WeakZipFileFactory; -import org.apache.felix.framework.util.WeakZipFileFactory.WeakZipFile; -import org.osgi.framework.Constants; public class JarContent implements Content { - private static final int BUFSIZE = 4096; private static final transient String EMBEDDED_DIRECTORY = "-embedded"; private static final transient String LIBRARY_DIRECTORY = "-lib"; @@ -131,9 +129,6 @@ public class JarContent implements Conte public byte[] getEntryAsBytes(String name) throws IllegalStateException { // Get the embedded resource. - InputStream is = null; - ByteArrayOutputStream baos = null; - try { ZipEntry ze = m_zipFile.getEntry(name); @@ -141,19 +136,8 @@ public class JarContent implements Conte { return null; } - is = m_zipFile.getInputStream(ze); - if (is == null) - { - return null; - } - baos = new ByteArrayOutputStream(BUFSIZE); - byte[] buf = new byte[BUFSIZE]; - int n = 0; - while ((n = is.read(buf, 0, buf.length)) >= 0) - { - baos.write(buf, 0, n); - } - return baos.toByteArray(); + + return BundleCache.read(m_zipFile.getInputStream(ze), ze.getSize()); } catch (Exception ex) @@ -163,23 +147,6 @@ public class JarContent implements Conte "JarContent: Unable to read bytes for file " + name + " in ZIP file " + m_file.getAbsolutePath(), ex); return null; } - finally - { - try - { - if (baos != null) baos.close(); - } - catch (Exception ex) - { - } - try - { - if (is != null) is.close(); - } - catch (Exception ex) - { - } - } } public InputStream getEntryAsStream(String name) @@ -348,20 +315,10 @@ public class JarContent implements Conte } else { - InputStream is = null; - try { - is = new BufferedInputStream( - m_zipFile.getInputStream(ze), - BundleCache.BUFSIZE); - if (is == null) - { - throw new IOException("No input stream: " + entryName); - } - // Create the file. - BundleCache.copyStreamToFile(is, libFile); + BundleCache.copyStreamToFile(m_zipFile.getInputStream(ze), libFile); // Perform exec permission command on extracted library // if one is configured. @@ -398,17 +355,6 @@ public class JarContent implements Conte Logger.LOG_ERROR, "Extracting native library.", ex); } - finally - { - try - { - if (is != null) is.close(); - } - catch (IOException ex) - { - // Not much we can do. - } - } } } else @@ -435,7 +381,6 @@ public class JarContent implements Conte /** * This method extracts an embedded JAR file from the bundle's * JAR file. - * @param id the identifier of the bundle that owns the embedded JAR file. * @param jarPath the path to the embedded JAR file inside the bundle JAR file. **/ private void extractEmbeddedJar(String jarPath) @@ -453,44 +398,30 @@ public class JarContent implements Conte if (!BundleCache.getSecureAction().fileExists(jarFile)) { - InputStream is = null; - try + // Make sure class path entry is a JAR file. + ZipEntry ze = m_zipFile.getEntry(jarPath); + if (ze == null) { - // Make sure class path entry is a JAR file. - ZipEntry ze = m_zipFile.getEntry(jarPath); - if (ze == null) - { - return; - } - // If the zip entry is a directory, then ignore it since - // we don't need to extact it; otherwise, it points to an - // embedded JAR file, so extract it. - else if (!ze.isDirectory()) + return; + } + // If the zip entry is a directory, then ignore it since + // we don't need to extact it; otherwise, it points to an + // embedded JAR file, so extract it. + else if (!ze.isDirectory()) + { + // Make sure that the embedded JAR's parent directory exists; + // it may be in a sub-directory. + File jarDir = jarFile.getParentFile(); + if (!BundleCache.getSecureAction().fileExists(jarDir)) { - // Make sure that the embedded JAR's parent directory exists; - // it may be in a sub-directory. - File jarDir = jarFile.getParentFile(); - if (!BundleCache.getSecureAction().fileExists(jarDir)) - { - if (!BundleCache.getSecureAction().mkdirs(jarDir)) - { - throw new IOException("Unable to create embedded JAR directory."); - } - } - - // Extract embedded JAR into its directory. - is = new BufferedInputStream(m_zipFile.getInputStream(ze), BundleCache.BUFSIZE); - if (is == null) + if (!BundleCache.getSecureAction().mkdirs(jarDir)) { - throw new IOException("No input stream: " + jarPath); + throw new IOException("Unable to create embedded JAR directory."); } - // Copy the file. - BundleCache.copyStreamToFile(is, jarFile); } - } - finally - { - if (is != null) is.close(); + + // Extract embedded JAR into its directory. + BundleCache.copyStreamToFile(m_zipFile.getInputStream(ze), jarFile); } } } Modified: felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java?rev=1826714&r1=1826713&r2=1826714&view=diff ============================================================================== --- felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java (original) +++ felix/trunk/osgi-r7/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java Wed Mar 14 13:28:58 2018 @@ -18,22 +18,21 @@ */ package org.apache.felix.framework.cache; +import org.apache.felix.framework.Logger; +import org.apache.felix.framework.util.StringMap; +import org.apache.felix.framework.util.Util; +import org.apache.felix.framework.util.WeakZipFileFactory; +import org.apache.felix.framework.util.WeakZipFileFactory.WeakZipFile; + import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.lang.ref.SoftReference; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.Map; import java.util.zip.ZipEntry; -import org.apache.felix.framework.Logger; -import org.apache.felix.framework.util.StringMap; -import org.apache.felix.framework.util.Util; -import org.apache.felix.framework.util.WeakZipFileFactory; -import org.apache.felix.framework.util.WeakZipFileFactory.WeakZipFile; - /** * <p> * This class implements a bundle archive revision for a standard bundle @@ -98,11 +97,12 @@ class JarRevision extends BundleArchiveR public Map<String, Object> getManifestHeader() throws Exception { - // Create a case insensitive map of manifest attributes. - Map<String, Object> headers = new StringMap(); - // Read and parse headers. - getMainAttributes(headers, m_zipFile); - return headers; + // Read and parse headers into a case insensitive map of manifest attributes and return it. + ZipEntry manifestEntry = m_zipFile.getEntry("META-INF/MANIFEST.MF"); + + Map<String, Object> manifest = manifestEntry != null ? BundleCache.getMainAttributes(new StringMap(), m_zipFile.getInputStream(manifestEntry), manifestEntry.getSize()) : null; + + return manifest; } public synchronized Content getContent() throws Exception @@ -186,129 +186,4 @@ class JarRevision extends BundleArchiveR if (is != null) is.close(); } } - - private static final ThreadLocal m_defaultBuffer = new ThreadLocal(); - private static final int DEFAULT_BUFFER = 1024 * 64; - - // Parse the main attributes of the manifest of the given jarfile. - // The idea is to not open the jar file as a java.util.jarfile but - // read the mainfest from the zipfile directly and parse it manually - // to use less memory and be faster. - private static void getMainAttributes(Map<String, Object> result, WeakZipFile zipFile) throws Exception - { - ZipEntry entry = zipFile.getEntry("META-INF/MANIFEST.MF"); - - // Get a buffer for this thread if there is one already otherwise, - // create one of size DEFAULT_BUFFER (64K) if the manifest is less - // than 64k or of the size of the manifest. - SoftReference ref = (SoftReference) m_defaultBuffer.get(); - byte[] bytes = null; - if (ref != null) - { - bytes = (byte[]) ref.get(); - } - int size = (int) entry.getSize(); - if (bytes == null) - { - bytes = new byte[size > DEFAULT_BUFFER ? size : DEFAULT_BUFFER]; - m_defaultBuffer.set(new SoftReference(bytes)); - } - else if (size > bytes.length) - { - bytes = new byte[size]; - m_defaultBuffer.set(new SoftReference(bytes)); - } - - // Now read in the manifest in one go into the bytes array. - // The InputStream is already - // buffered and can handle up to 64K buffers in one go. - InputStream is = null; - try - { - is = zipFile.getInputStream(entry); - int i = is.read(bytes); - while (i < size) - { - i += is.read(bytes, i, bytes.length - i); - } - } - finally - { - is.close(); - } - - // Now parse the main attributes. The idea is to do that - // without creating new byte arrays. Therefore, we read through - // the manifest bytes inside the bytes array and write them back into - // the same array unless we don't need them (e.g., \r\n and \n are skipped). - // That allows us to create the strings from the bytes array without the skipped - // chars. We stopp as soon as we see a blankline as that denotes that the main - //attributes part is finished. - String key = null; - int last = 0; - int current = 0; - for (int i = 0; i < size; i++) - { - // skip \r and \n if it is follows by another \n - // (we catch the blank line case in the next iteration) - if (bytes[i] == '\r') - { - if ((i + 1 < size) && (bytes[i + 1] == '\n')) - { - continue; - } - } - if (bytes[i] == '\n') - { - if ((i + 1 < size) && (bytes[i + 1] == ' ')) - { - i++; - continue; - } - } - // If we don't have a key yet and see the first : we parse it as the key - // and skip the :<blank> that follows it. - if ((key == null) && (bytes[i] == ':')) - { - key = new String(bytes, last, (current - last), "UTF-8"); - if ((i + 1 < size) && (bytes[i + 1] == ' ')) - { - last = current + 1; - continue; - } - else - { - throw new Exception( - "Manifest error: Missing space separator - " + key); - } - } - // if we are at the end of a line - if (bytes[i] == '\n') - { - // and it is a blank line stop parsing (main attributes are done) - if ((last == current) && (key == null)) - { - break; - } - // Otherwise, parse the value and add it to the map (we throw an - // exception if we don't have a key or the key already exist. - String value = new String(bytes, last, (current - last), "UTF-8"); - if (key == null) - { - throw new Exception("Manifst error: Missing attribute name - " + value); - } - else if (result.put(key, value) != null) - { - throw new Exception("Manifst error: Duplicate attribute name - " + key); - } - last = current; - key = null; - } - else - { - // write back the byte if it needs to be included in the key or the value. - bytes[current++] = bytes[i]; - } - } - } } \ No newline at end of file