Ranides Atterwim 3 tahun lalu
induk
melakukan
b099246e9c
1 mengubah file dengan 91 tambahan dan 33 penghapusan
  1. 91 33
      assira.core/src/main/java/net/ranides/assira/io/ZipReader.java

+ 91 - 33
assira.core/src/main/java/net/ranides/assira/io/ZipReader.java

@@ -24,7 +24,7 @@ public class ZipReader {
      * @throws IOException on error
      */
     public static JarInputStream openJAR(InputStream istream) throws IOException {
-        return new JarInputStream(new ZipInputStream2(istream));
+        return new JarInputStream(new SkipInputStream(istream));
     }
 
     /**
@@ -34,57 +34,115 @@ public class ZipReader {
      * @return ZipInputStream
      */
     public static ZipInputStream openZIP(InputStream istream) {
-        return new ZipInputStream(new ZipInputStream2(istream));
+        return new ZipInputStream(new SkipInputStream(istream));
     }
 
-    private static class ZipInputStream2 extends FilterInputStream {
+    private static class SkipInputStream extends FilterInputStream {
 
-        public static final byte[] ZIP_LOCAL = { 0x50, 0x4b, 0x03, 0x04 };
+        private static final int BUFFER_SIZE = 512;
 
-        protected int ip;
+        private static final byte[] ZIP_LOCAL = { 0x50, 0x4b, 0x03, 0x04 };
 
-        protected int op;
+        private byte[] buffer;
+        private int bufferSize;
+        private int bufferPosition;
 
-        public ZipInputStream2(InputStream is) {
+        private int signatureFound;
+        private int signatureReturned;
+
+        public SkipInputStream(InputStream is) {
             super(is);
+            this.buffer = new byte[BUFFER_SIZE];
+            this.bufferSize = 0;
+            this.bufferPosition = 0;
         }
 
+
         public int read() throws IOException {
-            // skip data until full ZIP_LOCAL is found
-            // do nothing if it is already found
-            while(ip < ZIP_LOCAL.length) {
-                int c = super.read();
-                if (c == ZIP_LOCAL[ip]) {
-                    ip++;
+            // if we already returned ZIP_LOCAL and data from skipBuffer, then delegate to ordinary I/O
+            if(buffer == null) {
+                return super.read();
+            }
+
+            // If ZIP_LOCAL is not found, search it by using buffered "read"
+            // We are going to skip everything before ZIP_LOCAL, including ZIP_LOCAL itself.
+            // Skipping ZIP_LOCAL is necessary because it can be stored across two consecutive buffered reads
+            while(signatureFound < ZIP_LOCAL.length) {
+                int c = readbuf();
+                if(c == -1) {
+                    return -1;
+                }
+                if (c == ZIP_LOCAL[signatureFound]) {
+                    signatureFound++;
                 } else {
-                    ip = 0;
+                    signatureFound = 0;
                 }
             }
-            // despite the fact, that we physically skipped signature,
-            // we should return it to the reader byte by byte
-            return op < ZIP_LOCAL.length ? ZIP_LOCAL[op++] : super.read();
+
+            // "ZIP_LOCAL" is consumed from "buffer"
+            // we need to return it literally, by reading ZIP_LOCAL byte by byte
+            if(signatureReturned < ZIP_LOCAL.length) {
+                return ZIP_LOCAL[signatureReturned++];
+            }
+
+            // buffer can contain some bytes after ZIP_LOCAL, let's return them byte by byte
+            if(bufferPosition < bufferSize) {
+                // "readbuf" won't fill new buffer because it fills it only if bufferPosition == bufferSize
+                return readbuf();
+            } else {
+                // if we returned whole buffer, lets clear it and delegate to ordinary I/O
+                // method will detect empty buffer on next calls
+                buffer = null;
+                return super.read();
+            }
+        }
+
+        private int readbuf() throws IOException {
+            // read new block if current one is exhausted
+            if(bufferPosition >= bufferSize) {
+                bufferSize = super.read(buffer, 0, buffer.length);
+                if(bufferSize == -1) {
+                    buffer = null;
+                    return -1;
+                }
+                bufferPosition = 0;
+            }
+            // return data from buffer, byte by byte
+            return buffer[bufferPosition++];
         }
 
         @Override
-        public int read(byte[] b, int off, int len) throws IOException {
-            // if we already returned signature, then just return data
-            if (op == ZIP_LOCAL.length) {
-                return super.read(b, off, len);
+        public int read(byte[] output, int off, int len) throws IOException {
+            // if we already returned signature, then use ordinary I/O
+            if (buffer == null) {
+                return super.read(output, off, len);
             }
-            // if we did not return signature, then we delegate work to our "read"
-            // byte by byte and fill buffer with correct bytes
-            //
-            // "read" will skip prefix and will return signature followed by content
+
+            // read byte by byte from our buffer
+            int rem = Math.min(len, bufferRemaining());
             int count = 0;
-            // @todo #89
-            // probably we will miscalculate number of bytes if signature is partially read
-            // we should use here
-            //      int zip = ZIP_LOCAL.length-op
-            //      while (count < Math.min(len, zip)) ...
-            while (count < Math.min(len, ZIP_LOCAL.length)) {
-                b[count++] = (byte)read();
+            while (count < rem) {
+                byte b = (byte) read();
+                if(b == -1) {
+                    return count;
+                }
+                output[off + count++] = b;
+            }
+
+            // if we read "len" bytes, then just return
+            if(count == len) {
+                return count;
+            }
+
+            // if we read less than "len" bytes, then use ordinary I/O for read remaining bytes
+            return count + super.read(output, off + count, len - count);
+        }
+
+        private int bufferRemaining() {
+            if(signatureReturned != ZIP_LOCAL.length) {
+                return buffer.length + ZIP_LOCAL.length;
             }
-            return count;
+            return bufferSize - bufferPosition;
         }
     }